Kstars

darklibrary.cpp
1 /*
2  SPDX-FileCopyrightText: 2016 Jasem Mutlaq <[email protected]>
3 
4  SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "darklibrary.h"
8 #include "Options.h"
9 
10 #include "ekos/manager.h"
11 #include "ekos/capture/capture.h"
12 #include "ekos/capture/sequencejob.h"
13 #include "ekos/auxiliary/opticaltrainmanager.h"
14 #include "ekos/auxiliary/profilesettings.h"
15 #include "ekos/auxiliary/opticaltrainsettings.h"
16 #include "kstars.h"
17 #include "kspaths.h"
18 #include "kstarsdata.h"
19 #include "fitsviewer/fitsdata.h"
20 #include "fitsviewer/fitsview.h"
21 
22 #include <QDesktopServices>
23 #include <QSqlRecord>
24 #include <QSqlTableModel>
25 #include <QStatusBar>
26 #include <algorithm>
27 #include <array>
28 
29 namespace Ekos
30 {
31 DarkLibrary *DarkLibrary::_DarkLibrary = nullptr;
32 
33 DarkLibrary *DarkLibrary::Instance()
34 {
35  if (_DarkLibrary == nullptr)
36  _DarkLibrary = new DarkLibrary(Manager::Instance());
37 
38  return _DarkLibrary;
39 }
40 
41 DarkLibrary::DarkLibrary(QWidget *parent) : QDialog(parent)
42 {
43  setupUi(this);
44 
45  m_StatusBar = new QStatusBar(this);
46  m_StatusLabel = new QLabel(i18n("Idle"), this);
47  m_FileLabel = new QLabel(this);
48  m_FileLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
49 
50  m_StatusBar->insertPermanentWidget(0, m_StatusLabel);
51  m_StatusBar->insertPermanentWidget(1, m_FileLabel, 1);
52  mainLayout->addWidget(m_StatusBar);
53 
54  QDir writableDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
55  writableDir.mkpath("darks");
56  writableDir.mkpath("defectmaps");
57 
58  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
59  // Dark Generation Connections
60  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
61  m_CurrentDarkFrame.reset(new FITSData(), &QObject::deleteLater);
62 
63  connect(darkTableView, &QAbstractItemView::doubleClicked, this, [this](QModelIndex index)
64  {
65  loadIndexInView(index.row());
66  });
67  connect(openDarksFolderB, &QPushButton::clicked, this, &DarkLibrary::openDarksFolder);
68  connect(clearAllB, &QPushButton::clicked, this, &DarkLibrary::clearAll);
69  connect(clearRowB, &QPushButton::clicked, this, &DarkLibrary::clearRow);
70  connect(clearExpiredB, &QPushButton::clicked, this, &DarkLibrary::clearExpired);
71  connect(refreshB, &QPushButton::clicked, this, &DarkLibrary::reloadDarksFromDatabase);
72 
73  connect(&m_DarkFrameFutureWatcher, &QFutureWatcher<bool>::finished, this, [this]()
74  {
75  // If loading is successful, then set it in current dark view
76  if (m_DarkFrameFutureWatcher.result())
77  {
78  m_DarkView->loadData(m_CurrentDarkFrame);
79  loadCurrentMasterDefectMap();
80  populateMasterMetedata();
81  }
82  else
83  m_FileLabel->setText(i18n("Failed to load %1: %2", m_MasterDarkFrameFilename, m_CurrentDarkFrame->getLastError()));
84 
85  });
86 
87  connect(masterDarksCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this](int index)
88  {
89  if (m_Camera)
90  DarkLibrary::loadCurrentMasterDark(m_Camera->getDeviceName(), index);
91  });
92 
93  connect(minExposureSpin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime);
94  connect(maxExposureSpin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime);
95  connect(exposureStepSin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime);
96 
97  connect(minTemperatureSpin, &QDoubleSpinBox::editingFinished, this, [this]()
98  {
99  maxTemperatureSpin->setMinimum(minTemperatureSpin->value());
100  countDarkTotalTime();
101  });
102  connect(maxTemperatureSpin, &QDoubleSpinBox::editingFinished, this, [this]()
103  {
104  minTemperatureSpin->setMaximum(maxTemperatureSpin->value());
105  countDarkTotalTime();
106  });
107  connect(temperatureStepSpin, &QDoubleSpinBox::editingFinished, this, [this]()
108  {
109  maxTemperatureSpin->setMinimum(minTemperatureSpin->value());
110  minTemperatureSpin->setMaximum(maxTemperatureSpin->value());
111  countDarkTotalTime();
112  });
113 
114  connect(countSpin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime);
115 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
116  connect(binningButtonGroup, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::buttonToggled),
117  this, [this](int, bool)
118 #else
119  connect(binningButtonGroup, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::idToggled),
120  this, [this](int, bool)
121 #endif
122  {
123  countDarkTotalTime();
124  });
125 
126  connect(startB, &QPushButton::clicked, this, &DarkLibrary::start);
127  connect(stopB, &QPushButton::clicked, this, &DarkLibrary::stop);
128 
129  KStarsData::Instance()->userdb()->GetAllDarkFrames(m_DarkFramesDatabaseList);
130  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
131  // Defect Map Connections
132  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
133  connect(darkTabsWidget, &QTabWidget::currentChanged, this, [this](int index)
134  {
135  m_DarkView->setDefectMapEnabled(index == 1 && m_CurrentDefectMap);
136  });
137  connect(aggresivenessHotSlider, &QSlider::valueChanged, aggresivenessHotSpin, &QSpinBox::setValue);
138  connect(aggresivenessColdSlider, &QSlider::valueChanged, aggresivenessColdSpin, &QSpinBox::setValue);
139  connect(hotPixelsEnabled, &QCheckBox::toggled, this, [this](bool toggled)
140  {
141  if (m_CurrentDefectMap)
142  m_CurrentDefectMap->setProperty("HotEnabled", toggled);
143  });
144  connect(coldPixelsEnabled, &QCheckBox::toggled, this, [this](bool toggled)
145  {
146  if (m_CurrentDefectMap)
147  m_CurrentDefectMap->setProperty("ColdEnabled", toggled);
148  });
149  connect(generateMapB, &QPushButton::clicked, this, [this]()
150  {
151  if (m_CurrentDefectMap)
152  {
153  m_CurrentDefectMap->setProperty("HotPixelAggressiveness", aggresivenessHotSpin->value());
154  m_CurrentDefectMap->setProperty("ColdPixelAggressiveness", aggresivenessColdSpin->value());
155  m_CurrentDefectMap->filterPixels();
156  emit newFrame(m_DarkView);
157  }
158  });
159  connect(resetMapParametersB, &QPushButton::clicked, this, [this]()
160  {
161  if (m_CurrentDefectMap)
162  {
163  aggresivenessHotSlider->setValue(75);
164  aggresivenessColdSlider->setValue(75);
165  m_CurrentDefectMap->setProperty("HotPixelAggressiveness", 75);
166  m_CurrentDefectMap->setProperty("ColdPixelAggressiveness", 75);
167  m_CurrentDefectMap->filterPixels();
168  }
169  });
170  connect(saveMapB, &QPushButton::clicked, this, &DarkLibrary::saveDefectMap);
171  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
172  // Settings & Initialization
173  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
174  m_RememberFITSViewer = Options::useFITSViewer();
175  m_RememberSummaryView = Options::useSummaryPreview();
176  initView();
177 
178  loadGlobalSettings();
179 
180  connectSettings();
181 
182  setupOpticalTrainManager();
183 }
184 
185 DarkLibrary::~DarkLibrary()
186 {
187 }
188 
189 ///////////////////////////////////////////////////////////////////////////////////////
190 ///
191 ///////////////////////////////////////////////////////////////////////////////////////
192 void DarkLibrary::refreshFromDB()
193 {
194  KStarsData::Instance()->userdb()->GetAllDarkFrames(m_DarkFramesDatabaseList);
195 }
196 
197 ///////////////////////////////////////////////////////////////////////////////////////
198 ///
199 ///////////////////////////////////////////////////////////////////////////////////////
200 bool DarkLibrary::findDarkFrame(ISD::CameraChip *m_TargetChip, double duration, QSharedPointer<FITSData> &darkData)
201 {
202  QVariantMap bestCandidate;
203  for (auto &map : m_DarkFramesDatabaseList)
204  {
205  // First check CCD name matches and check if we are on the correct chip
206  if (map["ccd"].toString() == m_TargetChip->getCCD()->getDeviceName() &&
207  map["chip"].toInt() == static_cast<int>(m_TargetChip->getType()))
208  {
209  // Match Gain
210  int gain = getGain();
211  if (gain >= 0 && map["gain"].toInt() != gain)
212  continue;
213 
214  // Match ISO
215  QString isoValue;
216  if (m_TargetChip->getISOValue(isoValue) && map["iso"].toString() != isoValue)
217  continue;
218 
219  // Match binning
220  int binX = 1, binY = 1;
221  m_TargetChip->getBinning(&binX, &binY);
222 
223  // Then check if binning is the same
224  if (map["binX"].toInt() != binX || map["binY"].toInt() != binY)
225  continue;
226 
227  // If camera has an active cooler, then we check temperature against the absolute threshold.
228  if (m_TargetChip->getCCD()->hasCoolerControl())
229  {
230  double temperature = 0;
231  m_TargetChip->getCCD()->getTemperature(&temperature);
232  double darkTemperature = map["temperature"].toDouble();
233  // If different is above threshold, it is completely rejected.
234  if (darkTemperature != INVALID_VALUE && fabs(darkTemperature - temperature) > maxDarkTemperatureDiff->value())
235  continue;
236  }
237 
238  if (bestCandidate.isEmpty())
239  {
240  bestCandidate = map;
241  continue;
242  }
243 
244  // We try to find the best frame
245  // Frame closest in exposure duration wins
246  // Frame with temperature closest to stored temperature wins (if temperature is reported)
247  uint32_t thisMapScore = 0;
248  uint32_t bestCandidateScore = 0;
249 
250  // Else we check for the closest passive temperature
251  if (m_TargetChip->getCCD()->hasCooler())
252  {
253  double temperature = 0;
254  m_TargetChip->getCCD()->getTemperature(&temperature);
255  double diffMap = std::fabs(temperature - map["temperature"].toDouble());
256  double diffBest = std::fabs(temperature - bestCandidate["temperature"].toDouble());
257  // Prefer temperatures closest to target
258  if (diffMap < diffBest)
259  thisMapScore++;
260  else if (diffBest < diffMap)
261  bestCandidateScore++;
262  }
263 
264  // Duration has a higher score priority over temperature
265  {
266  double diffMap = std::fabs(map["duration"].toDouble() - duration);
267  double diffBest = std::fabs(bestCandidate["duration"].toDouble() - duration);
268  if (diffMap < diffBest)
269  thisMapScore += 5;
270  else if (diffBest < diffMap)
271  bestCandidateScore += 5;
272  }
273 
274  // More recent has a higher score than older.
275  {
277  int64_t diffMap = map["timestamp"].toDateTime().secsTo(now);
278  int64_t diffBest = bestCandidate["timestamp"].toDateTime().secsTo(now);
279  if (diffMap < diffBest)
280  thisMapScore++;
281  else if (diffBest < diffMap)
282  bestCandidateScore++;
283  }
284 
285  // Find candidate with closest time in case we have multiple defect maps
286  if (thisMapScore > bestCandidateScore)
287  bestCandidate = map;
288  }
289  }
290 
291  if (bestCandidate.isEmpty())
292  return false;
293 
294  if (fabs(bestCandidate["duration"].toDouble() - duration) > 3)
295  emit i18n("Using available dark frame with %1 seconds exposure. Please take a dark frame with %1 seconds exposure for more accurate results.",
296  QString::number(bestCandidate["duration"].toDouble(), 'f', 1),
297  QString::number(duration, 'f', 1));
298 
299  QString filename = bestCandidate["filename"].toString();
300 
301  // Finally check if the duration is acceptable
302  QDateTime frameTime = bestCandidate["timestamp"].toDateTime();
303  if (frameTime.daysTo(QDateTime::currentDateTime()) > Options::darkLibraryDuration())
304  {
305  emit i18n("Dark frame %s is expired. Please create new master dark.", filename);
306  return false;
307  }
308 
309  if (m_CachedDarkFrames.contains(filename))
310  {
311  darkData = m_CachedDarkFrames[filename];
312  return true;
313  }
314 
315  // Before adding to cache, clear the cache if memory drops too low.
316  auto memoryMB = KSUtils::getAvailableRAM() / 1e6;
317  if (memoryMB < CACHE_MEMORY_LIMIT)
318  m_CachedDarkFrames.clear();
319 
320  // Finally we made it, let's put it in the hash
321  if (cacheDarkFrameFromFile(filename))
322  {
323  darkData = m_CachedDarkFrames[filename];
324  return true;
325  }
326 
327  // Remove bad dark frame
328  emit newLog(i18n("Removing bad dark frame file %1", filename));
329  m_CachedDarkFrames.remove(filename);
330  QFile::remove(filename);
331  KStarsData::Instance()->userdb()->DeleteDarkFrame(filename);
332  return false;
333 
334 }
335 
336 ///////////////////////////////////////////////////////////////////////////////////////
337 ///
338 ///////////////////////////////////////////////////////////////////////////////////////
339 bool DarkLibrary::findDefectMap(ISD::CameraChip *m_TargetChip, double duration, QSharedPointer<DefectMap> &defectMap)
340 {
341  QVariantMap bestCandidate;
342  for (auto &map : m_DarkFramesDatabaseList)
343  {
344  if (map["defectmap"].toString().isEmpty())
345  continue;
346 
347  // First check CCD name matches and check if we are on the correct chip
348  if (map["ccd"].toString() == m_TargetChip->getCCD()->getDeviceName() &&
349  map["chip"].toInt() == static_cast<int>(m_TargetChip->getType()))
350  {
351  int binX, binY;
352  m_TargetChip->getBinning(&binX, &binY);
353 
354  // Then check if binning is the same
355  if (map["binX"].toInt() == binX && map["binY"].toInt() == binY)
356  {
357  if (bestCandidate.isEmpty())
358  {
359  bestCandidate = map;
360  continue;
361  }
362 
363  // We try to find the best frame
364  // Frame closest in exposure duration wins
365  // Frame with temperature closest to stored temperature wins (if temperature is reported)
366  uint32_t thisMapScore = 0;
367  uint32_t bestCandidateScore = 0;
368 
369  // Else we check for the closest passive temperature
370  if (m_TargetChip->getCCD()->hasCooler())
371  {
372  double temperature = 0;
373  m_TargetChip->getCCD()->getTemperature(&temperature);
374  double diffMap = std::fabs(temperature - map["temperature"].toDouble());
375  double diffBest = std::fabs(temperature - bestCandidate["temperature"].toDouble());
376  // Prefer temperatures closest to target
377  if (diffMap < diffBest)
378  thisMapScore++;
379  else if (diffBest < diffMap)
380  bestCandidateScore++;
381  }
382 
383  // Duration has a higher score priority over temperature
384  double diffMap = std::fabs(map["duration"].toDouble() - duration);
385  double diffBest = std::fabs(bestCandidate["duration"].toDouble() - duration);
386  if (diffMap < diffBest)
387  thisMapScore += 2;
388  else if (diffBest < diffMap)
389  bestCandidateScore += 2;
390 
391  // Find candidate with closest time in case we have multiple defect maps
392  if (thisMapScore > bestCandidateScore)
393  bestCandidate = map;
394  }
395  }
396  }
397 
398 
399  if (bestCandidate.isEmpty())
400  return false;
401 
402 
403  QString darkFilename = bestCandidate["filename"].toString();
404  QString defectFilename = bestCandidate["defectmap"].toString();
405 
406  if (darkFilename.isEmpty() || defectFilename.isEmpty())
407  return false;
408 
409  if (m_CachedDefectMaps.contains(darkFilename))
410  {
411  defectMap = m_CachedDefectMaps[darkFilename];
412  return true;
413  }
414 
415  // Finally we made it, let's put it in the hash
416  if (cacheDefectMapFromFile(darkFilename, defectFilename))
417  {
418  defectMap = m_CachedDefectMaps[darkFilename];
419  return true;
420  }
421  else
422  {
423  // Remove bad dark frame
424  emit newLog(i18n("Failed to load defect map %1", defectFilename));
425  return false;
426  }
427 }
428 
429 ///////////////////////////////////////////////////////////////////////////////////////
430 ///
431 ///////////////////////////////////////////////////////////////////////////////////////
432 bool DarkLibrary::cacheDefectMapFromFile(const QString &key, const QString &filename)
433 {
435  oneMap.reset(new DefectMap());
436 
437  if (oneMap->load(filename))
438  {
439  oneMap->filterPixels();
440  m_CachedDefectMaps[key] = oneMap;
441  return true;
442  }
443 
444  emit newLog(i18n("Failed to load defect map file %1", filename));
445  return false;
446 }
447 
448 ///////////////////////////////////////////////////////////////////////////////////////
449 ///
450 ///////////////////////////////////////////////////////////////////////////////////////
451 bool DarkLibrary::cacheDarkFrameFromFile(const QString &filename)
452 {
454  data.reset(new FITSData(FITS_CALIBRATE), &QObject::deleteLater);
455  QFuture<bool> rc = data->loadFromFile(filename);
456 
457  rc.waitForFinished();
458  if (rc.result())
459  {
460  m_CachedDarkFrames[filename] = data;
461  }
462  else
463  {
464  emit newLog(i18n("Failed to load dark frame file %1", filename));
465  }
466 
467  return rc;
468 }
469 
470 ///////////////////////////////////////////////////////////////////////////////////////
471 ///
472 ///////////////////////////////////////////////////////////////////////////////////////
473 void DarkLibrary::processNewImage(SequenceJob *job, const QSharedPointer<FITSData> &data)
474 {
475  Q_UNUSED(data)
476  if (job->getStatus() == JOB_IDLE)
477  return;
478 
479  if (job->getCompleted() == job->getCoreProperty(SequenceJob::SJ_Count).toInt())
480  {
481  QJsonObject metadata
482  {
483  {"camera", m_Camera->getDeviceName()},
484  {"chip", m_TargetChip->getType()},
485  {"binx", job->getCoreProperty(SequenceJob::SJ_Binning).toPoint().x()},
486  {"biny", job->getCoreProperty(SequenceJob::SJ_Binning).toPoint().y()},
487  {"duration", job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble()}
488  };
489 
490  // Record temperature
491  double value = 0;
492  bool success = m_Camera->getTemperature(&value);
493  if (success)
494  metadata["temperature"] = value;
495 
496  success = m_Camera->hasGain() && m_Camera->getGain(&value);
497  if (success)
498  metadata["gain"] = value;
499 
500  QString isoValue;
501  success = m_TargetChip->getISOValue(isoValue);
502  if (success)
503  metadata["iso"] = isoValue;
504 
505  metadata["count"] = job->getCoreProperty(SequenceJob::SJ_Count).toInt();
506  generateMasterFrame(m_CurrentDarkFrame, metadata);
507  reloadDarksFromDatabase();
508  populateMasterMetedata();
509  }
510 }
511 
512 ///////////////////////////////////////////////////////////////////////////////////////
513 ///
514 ///////////////////////////////////////////////////////////////////////////////////////
515 void DarkLibrary::updateProperty(INDI::Property prop)
516 {
517  if (prop.getType() != INDI_BLOB)
518  return;
519 
520  auto bp = prop.getBLOB()->at(0);
521  QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<char *>(bp->getBlob()), bp->getSize());
522  if (!m_CurrentDarkFrame->loadFromBuffer(buffer, bp->getFormat()))
523  {
524  m_FileLabel->setText(i18n("Failed to process dark data."));
525  return;
526  }
527 
528  if (!m_DarkView->loadData(m_CurrentDarkFrame))
529  {
530  m_FileLabel->setText(i18n("Failed to load dark data."));
531  return;
532  }
533 
534  uint32_t totalElements = m_CurrentDarkFrame->channels() * m_CurrentDarkFrame->samplesPerChannel();
535  if (totalElements != m_DarkMasterBuffer.size())
536  m_DarkMasterBuffer.assign(totalElements, 0);
537 
538  aggregate(m_CurrentDarkFrame);
539  darkProgress->setValue(darkProgress->value() + 1);
540  m_StatusLabel->setText(i18n("Received %1/%2 images.", darkProgress->value(), darkProgress->maximum()));
541 }
542 
543 ///////////////////////////////////////////////////////////////////////////////////////
544 ///
545 ///////////////////////////////////////////////////////////////////////////////////////
546 void DarkLibrary::Release()
547 {
548  delete (_DarkLibrary);
549  _DarkLibrary = nullptr;
550 
551  // m_Cameras.clear();
552  // cameraS->clear();
553  // m_CurrentCamera = nullptr;
554 }
555 
556 ///////////////////////////////////////////////////////////////////////////////////////
557 ///
558 ///////////////////////////////////////////////////////////////////////////////////////
559 void DarkLibrary::closeEvent(QCloseEvent *ev)
560 {
561  Q_UNUSED(ev)
562  Options::setUseFITSViewer(m_RememberFITSViewer);
563  Options::setUseSummaryPreview(m_RememberSummaryView);
564  if (m_JobsGenerated)
565  {
566  m_JobsGenerated = false;
567  m_CaptureModule->clearSequenceQueue();
568  m_CaptureModule->setPresetSettings(m_PresetSettings);
569  m_CaptureModule->setFileSettings(m_FileSettings);
570  }
571 }
572 
573 ///////////////////////////////////////////////////////////////////////////////////////
574 ///
575 ///////////////////////////////////////////////////////////////////////////////////////
576 void DarkLibrary::setCompleted()
577 {
578  startB->setEnabled(true);
579  stopB->setEnabled(false);
580 
581  Options::setUseFITSViewer(m_RememberFITSViewer);
582  Options::setUseSummaryPreview(m_RememberSummaryView);
583  if (m_JobsGenerated)
584  {
585  m_JobsGenerated = false;
586  m_CaptureModule->clearSequenceQueue();
587  m_CaptureModule->setPresetSettings(m_PresetSettings);
588  m_CaptureModule->setFileSettings(m_FileSettings);
589  }
590 
591  m_Camera->disconnect(this);
592  m_CaptureModule->disconnect(this);
593 }
594 
595 ///////////////////////////////////////////////////////////////////////////////////////
596 ///
597 ///////////////////////////////////////////////////////////////////////////////////////
598 void DarkLibrary::clearExpired()
599 {
600  if (darkFramesModel->rowCount() == 0)
601  return;
602 
603  // Anything before this must go
604  QDateTime expiredDate = QDateTime::currentDateTime().addDays(darkLibraryDuration->value() * -1);
605 
606  auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName());
607  QSqlTableModel darkframe(nullptr, userdb);
608  darkframe.setEditStrategy(QSqlTableModel::OnManualSubmit);
609  darkframe.setTable("darkframe");
610  // Select all those that already expired.
611  darkframe.setFilter("ccd LIKE \'" + m_Camera->getDeviceName() + "\' AND timestamp < \'" + expiredDate.toString(
612  Qt::ISODate) + "\'");
613 
614  darkframe.select();
615 
616  // Now remove all the expired files from disk
617  for (int i = 0; i < darkframe.rowCount(); ++i)
618  {
619  QString oneFile = darkframe.record(i).value("filename").toString();
620  QFile::remove(oneFile);
621  QString defectMap = darkframe.record(i).value("defectmap").toString();
622  if (defectMap.isEmpty() == false)
623  QFile::remove(defectMap);
624 
625  }
626 
627  // And remove them from the database
628  darkframe.removeRows(0, darkframe.rowCount());
629  darkframe.submitAll();
630 
631  Ekos::DarkLibrary::Instance()->refreshFromDB();
632 
633  reloadDarksFromDatabase();
634 }
635 
636 ///////////////////////////////////////////////////////////////////////////////////////
637 ///
638 ///////////////////////////////////////////////////////////////////////////////////////
639 void DarkLibrary::clearBuffers()
640 {
641  m_CurrentDarkFrame.clear();
642  // Should clear existing view
643  m_CurrentDarkFrame.reset(new FITSData(), &QObject::deleteLater);
644  m_DarkView->clearData();
645  m_CurrentDefectMap.clear();
646 
647 }
648 ///////////////////////////////////////////////////////////////////////////////////////
649 ///
650 ///////////////////////////////////////////////////////////////////////////////////////
651 void DarkLibrary::clearAll()
652 {
653  if (darkFramesModel->rowCount() == 0)
654  return;
655 
657  i18n("Are you sure you want to delete all dark frames images and data?")) ==
659  return;
660 
661  auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName());
662  QSqlTableModel darkframe(nullptr, userdb);
663  darkFramesModel->setEditStrategy(QSqlTableModel::OnManualSubmit);
664  darkframe.setTable("darkframe");
665  darkframe.setFilter("ccd LIKE \'" + m_Camera->getDeviceName() + "\'");
666  darkFramesModel->select();
667 
668  // Now remove all the expired files from disk
669  for (int i = 0; i < darkframe.rowCount(); ++i)
670  {
671  QString oneFile = darkframe.record(i).value("filename").toString();
672  QFile::remove(oneFile);
673  QString defectMap = darkframe.record(i).value("defectmap").toString();
674  if (defectMap.isEmpty() == false)
675  QFile::remove(defectMap);
676 
677  }
678 
679  darkFramesModel->removeRows(0, darkFramesModel->rowCount());
680  darkFramesModel->submitAll();
681 
682  Ekos::DarkLibrary::Instance()->refreshFromDB();
683 
684  // Refesh db entries for other cameras
685  reloadDarksFromDatabase();
686 }
687 
688 ///////////////////////////////////////////////////////////////////////////////////////
689 ///
690 ///////////////////////////////////////////////////////////////////////////////////////
691 void DarkLibrary::clearRow(int index)
692 {
693  auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName());
694  if (index < 0)
695  index = darkTableView->currentIndex().row();
696 
697  QSqlRecord record = darkFramesModel->record(index);
698  QString filename = record.value("filename").toString();
699  QString defectMap = record.value("defectmap").toString();
700  QFile::remove(filename);
701  if (!defectMap.isEmpty())
702  QFile::remove(defectMap);
703 
704  darkFramesModel->removeRow(index);
705  darkFramesModel->submitAll();
706  userdb.close();
707 
708  darkTableView->selectionModel()->select(darkFramesModel->index(index - 1, 0), QItemSelectionModel::ClearAndSelect);
709 
710  refreshFromDB();
711  reloadDarksFromDatabase();
712 }
713 
714 ///////////////////////////////////////////////////////////////////////////////////////
715 ///
716 ///////////////////////////////////////////////////////////////////////////////////////
717 void DarkLibrary::openDarksFolder()
718 {
719  QString darkFilesPath = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("darks");
720 
722 }
723 
724 ///////////////////////////////////////////////////////////////////////////////////////
725 ///
726 ///////////////////////////////////////////////////////////////////////////////////////
727 void DarkLibrary::refreshDefectMastersList(const QString &camera)
728 {
729  if (darkFramesModel->rowCount() == 0)
730  return;
731 
732  masterDarksCombo->blockSignals(true);
733  masterDarksCombo->clear();
734 
735  for (int i = 0; i < darkFramesModel->rowCount(); ++i)
736  {
737  QSqlRecord record = darkFramesModel->record(i);
738 
739  if (record.value("ccd") != camera)
740  continue;
741 
742  auto binX = record.value("binX").toInt();
743  auto binY = record.value("binY").toInt();
744  auto temperature = record.value("temperature").toDouble();
745  auto duration = record.value("duration").toDouble();
746  auto gain = record.value("gain").toInt();
747  auto iso = record.value("iso").toString();
748  QString ts = record.value("timestamp").toString();
749 
750  QString entry = QString("%1 secs %2x%3")
751  .arg(QString::number(duration, 'f', 1))
752  .arg(QString::number(binX))
753  .arg(QString::number(binY));
754 
755  if (temperature > INVALID_VALUE)
756  entry.append(QString(" @ %1°").arg(QString::number(temperature, 'f', 1)));
757 
758  if (gain >= 0)
759  entry.append(QString(" G %1").arg(gain));
760  if (!iso.isEmpty())
761  entry.append(QString(" ISO %1").arg(iso));
762 
763  masterDarksCombo->addItem(entry);
764  }
765 
766  masterDarksCombo->blockSignals(false);
767 
768  //loadDefectMap();
769 
770 }
771 ///////////////////////////////////////////////////////////////////////////////////////
772 ///
773 ///////////////////////////////////////////////////////////////////////////////////////
774 void DarkLibrary::reloadDarksFromDatabase()
775 {
776  auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName());
777 
778  const QString camera = m_Camera->getDeviceName();
779 
780  delete (darkFramesModel);
781  delete (sortFilter);
782 
783  darkFramesModel = new QSqlTableModel(this, userdb);
784  darkFramesModel->setTable("darkframe");
785  darkFramesModel->setFilter(QString("ccd='%1'").arg(camera));
786  darkFramesModel->select();
787 
788  sortFilter = new QSortFilterProxyModel(this);
789  sortFilter->setSourceModel(darkFramesModel);
790  sortFilter->sort (0);
791  darkTableView->setModel (sortFilter);
792 
793  //darkTableView->setModel(darkFramesModel);
794  // Hide ID
795  darkTableView->hideColumn(0);
796  // Hide Chip
797  darkTableView->hideColumn(2);
798 
799  if (darkFramesModel->rowCount() == 0 && m_CurrentDarkFrame)
800  {
801  clearBuffers();
802  return;
803  }
804 
805  refreshDefectMastersList(camera);
806  loadCurrentMasterDark(camera);
807 }
808 
809 ///////////////////////////////////////////////////////////////////////////////////////
810 ///
811 ///////////////////////////////////////////////////////////////////////////////////////
812 void DarkLibrary::loadCurrentMasterDark(const QString &camera, int masterIndex)
813 {
814  // Do not process empty models
815  if (darkFramesModel->rowCount() == 0)
816  return;
817 
818  if (masterIndex == -1)
819  masterIndex = masterDarksCombo->currentIndex();
820 
821  if (masterIndex < 0 || masterIndex >= darkFramesModel->rowCount())
822  return;
823 
824  QSqlRecord record = darkFramesModel->record(masterIndex);
825  if (record.value("ccd") != camera)
826  return;
827  // Get the master dark frame file name
828  m_MasterDarkFrameFilename = record.value("filename").toString();
829 
830  if (m_MasterDarkFrameFilename.isEmpty())
831  return;
832 
833  // Get defect file name as well if available.
834  m_DefectMapFilename = record.value("defectmap").toString();
835 
836  // If current dark frame is different from target filename, then load from file
837  if (m_CurrentDarkFrame->filename() != m_MasterDarkFrameFilename)
838  m_DarkFrameFutureWatcher.setFuture(m_CurrentDarkFrame->loadFromFile(m_MasterDarkFrameFilename));
839  // If current dark frame is the same one loaded, then check if we need to reload defect map
840  else
841  loadCurrentMasterDefectMap();
842 }
843 
844 ///////////////////////////////////////////////////////////////////////////////////////
845 ///
846 ///////////////////////////////////////////////////////////////////////////////////////
847 void DarkLibrary::loadCurrentMasterDefectMap()
848 {
849  // Find if we have an existing map
850  if (m_CachedDefectMaps.contains(m_MasterDarkFrameFilename))
851  {
852  if (m_CurrentDefectMap != m_CachedDefectMaps.value(m_MasterDarkFrameFilename))
853  {
854  m_CurrentDefectMap = m_CachedDefectMaps.value(m_MasterDarkFrameFilename);
855  m_DarkView->setDefectMap(m_CurrentDefectMap);
856  m_CurrentDefectMap->setDarkData(m_CurrentDarkFrame);
857  }
858  }
859  // Create new defect map
860  else
861  {
862  m_CurrentDefectMap.reset(new DefectMap());
863  connect(m_CurrentDefectMap.data(), &DefectMap::pixelsUpdated, this, [this](uint32_t hot, uint32_t cold)
864  {
865  hotPixelsCount->setValue(hot);
866  coldPixelsCount->setValue(cold);
867  aggresivenessHotSlider->setValue(m_CurrentDefectMap->property("HotPixelAggressiveness").toInt());
868  aggresivenessColdSlider->setValue(m_CurrentDefectMap->property("ColdPixelAggressiveness").toInt());
869  });
870 
871  if (!m_DefectMapFilename.isEmpty())
872  cacheDefectMapFromFile(m_MasterDarkFrameFilename, m_DefectMapFilename);
873 
874  m_DarkView->setDefectMap(m_CurrentDefectMap);
875  m_CurrentDefectMap->setDarkData(m_CurrentDarkFrame);
876  }
877 }
878 
879 ///////////////////////////////////////////////////////////////////////////////////////
880 ///
881 ///////////////////////////////////////////////////////////////////////////////////////
882 void DarkLibrary::populateMasterMetedata()
883 {
884  if (m_CurrentDarkFrame.isNull())
885  return;
886 
887  QVariant value;
888  // TS
889  if (m_CurrentDarkFrame->getRecordValue("DATE-OBS", value))
890  masterTime->setText(value.toString());
891  // Temperature
892  if (m_CurrentDarkFrame->getRecordValue("CCD-TEMP", value) && value.toDouble() < 100)
893  masterTemperature->setText(QString::number(value.toDouble(), 'f', 1));
894  // Exposure
895  if (m_CurrentDarkFrame->getRecordValue("EXPTIME", value))
896  masterExposure->setText(value.toString());
897  // Median
898  {
899  double median = m_CurrentDarkFrame->getAverageMedian();
900  if (median > 0)
901  masterMedian->setText(QString::number(median, 'f', 1));
902  }
903  // Mean
904  {
905  double mean = m_CurrentDarkFrame->getAverageMean();
906  masterMean->setText(QString::number(mean, 'f', 1));
907  }
908  // Standard Deviation
909  {
910  double stddev = m_CurrentDarkFrame->getAverageStdDev();
911  masterDeviation->setText(QString::number(stddev, 'f', 1));
912  }
913 }
914 
915 ///////////////////////////////////////////////////////////////////////////////////////
916 ///
917 ///////////////////////////////////////////////////////////////////////////////////////
918 ///////////////////////////////////////////////////////////////////////////////////////
919 ///
920 ///////////////////////////////////////////////////////////////////////////////////////
921 void DarkLibrary::loadIndexInView(int row)
922 {
923  QSqlRecord record = darkFramesModel->record(row);
924  QString filename = record.value("filename").toString();
925  // Avoid duplicate loads
926  if (m_DarkView->imageData().isNull() || m_DarkView->imageData()->filename() != filename)
927  m_DarkView->loadFile(filename);
928 }
929 
930 ///////////////////////////////////////////////////////////////////////////////////////
931 ///
932 ///////////////////////////////////////////////////////////////////////////////////////
933 bool DarkLibrary::setCamera(ISD::Camera * device)
934 {
935  if (m_Camera == device)
936  return false;
937 
938  if (m_Camera)
939  m_Camera->disconnect(this);
940 
941  m_Camera = device;
942 
943  if (m_Camera)
944  {
945  darkTabsWidget->setEnabled(true);
946  checkCamera();
947  reloadDarksFromDatabase();
948  return true;
949  }
950  else
951  {
952  darkTabsWidget->setEnabled(false);
953  return false;
954  }
955 }
956 
957 ///////////////////////////////////////////////////////////////////////////////////////
958 ///
959 ///////////////////////////////////////////////////////////////////////////////////////
960 void DarkLibrary::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
961 {
962  if (m_Camera && m_Camera->getDeviceName() == device->getDeviceName())
963  {
964  m_Camera->disconnect(this);
965  m_Camera = nullptr;
966  }
967 }
968 
969 ///////////////////////////////////////////////////////////////////////////////////////
970 ///
971 ///////////////////////////////////////////////////////////////////////////////////////
972 void DarkLibrary::checkCamera()
973 {
974  if (!m_Camera)
975  return;
976 
977  auto device = m_Camera->getDeviceName();
978 
979  m_TargetChip = nullptr;
980  // FIXME TODO
981  // Need to figure guide head
982  if (device.contains("Guider"))
983  {
984  m_UseGuideHead = true;
985  m_TargetChip = m_Camera->getChip(ISD::CameraChip::GUIDE_CCD);
986  }
987 
988  if (m_TargetChip == nullptr)
989  {
990  m_UseGuideHead = false;
991  m_TargetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
992  }
993 
994  // Make sure we have a valid chip and valid base device.
995  // Make sure we are not in capture process.
996  if (!m_TargetChip || !m_TargetChip->getCCD() || m_TargetChip->isCapturing())
997  return;
998 
999  if (m_Camera->hasCoolerControl())
1000  {
1001  temperatureLabel->setEnabled(true);
1002  temperatureStepLabel->setEnabled(true);
1003  temperatureToLabel->setEnabled(true);
1004  temperatureStepSpin->setEnabled(true);
1005  minTemperatureSpin->setEnabled(true);
1006  maxTemperatureSpin->setEnabled(true);
1007 
1008  // Get default temperature
1009  double temperature = 0;
1010  // Update if no setting was previously set
1011  if (m_Camera->getTemperature(&temperature))
1012  {
1013  minTemperatureSpin->setValue(temperature);
1014  maxTemperatureSpin->setValue(temperature);
1015  }
1016 
1017  }
1018  else
1019  {
1020  temperatureLabel->setEnabled(false);
1021  temperatureStepLabel->setEnabled(false);
1022  temperatureToLabel->setEnabled(false);
1023  temperatureStepSpin->setEnabled(false);
1024  minTemperatureSpin->setEnabled(false);
1025  maxTemperatureSpin->setEnabled(false);
1026  }
1027 
1028  QStringList isoList = m_TargetChip->getISOList();
1029  captureISOS->blockSignals(true);
1030  captureISOS->clear();
1031 
1032  // No ISO range available
1033  if (isoList.isEmpty())
1034  {
1035  captureISOS->setEnabled(false);
1036  }
1037  else
1038  {
1039  captureISOS->setEnabled(true);
1040  captureISOS->addItems(isoList);
1041  captureISOS->setCurrentIndex(m_TargetChip->getISOIndex());
1042  }
1043  captureISOS->blockSignals(false);
1044 
1045  // Gain Check
1046  if (m_Camera->hasGain())
1047  {
1048  double min, max, step, value, targetCustomGain;
1049  m_Camera->getGainMinMaxStep(&min, &max, &step);
1050 
1051  // Allow the possibility of no gain value at all.
1052  GainSpinSpecialValue = min - step;
1053  captureGainN->setRange(GainSpinSpecialValue, max);
1054  captureGainN->setSpecialValueText(i18n("--"));
1055  captureGainN->setEnabled(true);
1056  captureGainN->setSingleStep(step);
1057  m_Camera->getGain(&value);
1058 
1059  targetCustomGain = getGain();
1060 
1061  // Set the custom gain if we have one
1062  // otherwise it will not have an effect.
1063  if (targetCustomGain > 0)
1064  captureGainN->setValue(targetCustomGain);
1065  else
1066  captureGainN->setValue(GainSpinSpecialValue);
1067 
1068  captureGainN->setReadOnly(m_Camera->getGainPermission() == IP_RO);
1069  }
1070  else
1071  captureGainN->setEnabled(false);
1072 
1073  countDarkTotalTime();
1074 
1075 }
1076 
1077 ///////////////////////////////////////////////////////////////////////////////////////
1078 ///
1079 ///////////////////////////////////////////////////////////////////////////////////////
1080 void DarkLibrary::countDarkTotalTime()
1081 {
1082  double temperatureCount = 1;
1083  if (m_Camera && m_Camera->hasCoolerControl() && std::abs(maxTemperatureSpin->value() - minTemperatureSpin->value()) > 0)
1084  temperatureCount = (std::abs((maxTemperatureSpin->value() - minTemperatureSpin->value())) / temperatureStepSpin->value()) +
1085  1;
1086  int binnings = 0;
1087  if (bin1Check->isChecked())
1088  binnings++;
1089  if (bin2Check->isChecked())
1090  binnings++;
1091  if (bin4Check->isChecked())
1092  binnings++;
1093 
1094  double darkTime = 0;
1095  int imagesCount = 0;
1096  for (double startExposure = minExposureSpin->value(); startExposure <= maxExposureSpin->value();
1097  startExposure += exposureStepSin->value())
1098  {
1099  darkTime += startExposure * temperatureCount * binnings * countSpin->value();
1100  imagesCount += temperatureCount * binnings * countSpin->value();
1101  }
1102 
1103  totalTime->setText(QString::number(darkTime / 60.0, 'f', 1));
1104  totalImages->setText(QString::number(imagesCount));
1105  darkProgress->setMaximum(imagesCount);
1106 
1107 }
1108 
1109 ///////////////////////////////////////////////////////////////////////////////////////
1110 ///
1111 ///////////////////////////////////////////////////////////////////////////////////////
1112 void DarkLibrary::generateDarkJobs()
1113 {
1114  // Always clear sequence queue before starting
1115  m_CaptureModule->clearSequenceQueue();
1116 
1117  if (m_JobsGenerated == false)
1118  {
1119  m_JobsGenerated = true;
1120  m_PresetSettings = m_CaptureModule->getPresetSettings();
1121  m_FileSettings = m_CaptureModule->getFileSettings();
1122  }
1123 
1124  QList<double> temperatures;
1125  if (m_Camera->hasCoolerControl() && std::fabs(maxTemperatureSpin->value() - minTemperatureSpin->value()) >= 0)
1126  {
1127  for (double oneTemperature = minTemperatureSpin->value(); oneTemperature <= maxTemperatureSpin->value();
1128  oneTemperature += temperatureStepSpin->value())
1129  {
1130  temperatures << oneTemperature;
1131  }
1132 
1133  // Enforce temperature set
1134  m_CaptureModule->setForceTemperature(true);
1135  }
1136  else
1137  {
1138  // Disable temperature set
1139  m_CaptureModule->setForceTemperature(false);
1140  temperatures << INVALID_VALUE;
1141  }
1142 
1143  QList<uint8_t> bins;
1144  if (bin1Check->isChecked())
1145  bins << 1;
1146  if (bin2Check->isChecked())
1147  bins << 2;
1148  if (bin4Check->isChecked())
1149  bins << 4;
1150 
1151  QList<double> exposures;
1152  for (double oneExposure = minExposureSpin->value(); oneExposure <= maxExposureSpin->value();
1153  oneExposure += exposureStepSin->value())
1154  {
1155  exposures << oneExposure;
1156  }
1157 
1159  QDir::separator();
1160 
1161 
1162  int sequence = 0;
1163  for (auto &oneTemperature : temperatures)
1164  {
1165  for (auto &oneExposure : exposures)
1166  {
1167  for (auto &oneBin : bins)
1168  {
1169  sequence++;
1170 
1171  QJsonObject settings;
1172 
1173  settings["optical_train"] = opticalTrainCombo->currentText();
1174  settings["exp"] = oneExposure;
1175  settings["bin"] = oneBin;
1176  settings["frameType"] = FRAME_DARK;
1177  settings["temperature"] = oneTemperature;
1178  if (captureGainN->isEnabled())
1179  settings["gain"] = captureGainN->value();
1180  if (captureISOS->isEnabled())
1181  settings["iso"] = captureISOS->currentIndex();
1182 
1183  QString directory = prefix + QString("sequence_%1").arg(sequence);
1184  QJsonObject fileSettings;
1185 
1186  fileSettings["directory"] = directory;
1187  m_CaptureModule->setPresetSettings(settings);
1188  m_CaptureModule->setFileSettings(fileSettings);
1189  m_CaptureModule->setCount(countSpin->value());
1190  m_CaptureModule->addJob();
1191  }
1192  }
1193  }
1194 }
1195 
1196 ///////////////////////////////////////////////////////////////////////////////////////
1197 ///
1198 ///////////////////////////////////////////////////////////////////////////////////////
1199 void DarkLibrary::execute()
1200 {
1201  m_DarkImagesCounter = 0;
1202  darkProgress->setValue(0);
1203  darkProgress->setTextVisible(true);
1204  connect(m_CaptureModule, &Capture::newImage, this, &DarkLibrary::processNewImage, Qt::UniqueConnection);
1205  connect(m_CaptureModule, &Capture::newStatus, this, &DarkLibrary::setCaptureState, Qt::UniqueConnection);
1206  connect(m_Camera, &ISD::Camera::propertyUpdated, this, &DarkLibrary::updateProperty, Qt::UniqueConnection);
1207 
1208  Options::setUseFITSViewer(false);
1209  Options::setUseSummaryPreview(false);
1210  startB->setEnabled(false);
1211  stopB->setEnabled(true);
1212  m_DarkView->reset();
1213  m_StatusLabel->setText(i18n("In progress..."));
1214  m_CaptureModule->start();
1215 
1216 }
1217 
1218 ///////////////////////////////////////////////////////////////////////////////////////
1219 ///
1220 ///////////////////////////////////////////////////////////////////////////////////////
1221 void DarkLibrary::stop()
1222 {
1223  m_CaptureModule->abort();
1224  darkProgress->setValue(0);
1225  m_DarkView->reset();
1226 }
1227 
1228 ///////////////////////////////////////////////////////////////////////////////////////
1229 ///
1230 ///////////////////////////////////////////////////////////////////////////////////////
1231 void DarkLibrary::initView()
1232 {
1233  m_DarkView.reset(new DarkView(darkWidget));
1234  m_DarkView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1235  m_DarkView->setBaseSize(darkWidget->size());
1236  m_DarkView->createFloatingToolBar();
1237  QVBoxLayout *vlayout = new QVBoxLayout();
1238  vlayout->addWidget(m_DarkView.get());
1239  darkWidget->setLayout(vlayout);
1240  connect(m_DarkView.get(), &DarkView::loaded, this, [this]()
1241  {
1242  emit newImage(m_DarkView->imageData());
1243  });
1244 }
1245 
1246 ///////////////////////////////////////////////////////////////////////////////////////
1247 ///
1248 ///////////////////////////////////////////////////////////////////////////////////////
1249 void DarkLibrary::aggregate(const QSharedPointer<FITSData> &data)
1250 {
1251  switch (data->dataType())
1252  {
1253  case TBYTE:
1254  aggregateInternal<uint8_t>(data);
1255  break;
1256 
1257  case TSHORT:
1258  aggregateInternal<int16_t>(data);
1259  break;
1260 
1261  case TUSHORT:
1262  aggregateInternal<uint16_t>(data);
1263  break;
1264 
1265  case TLONG:
1266  aggregateInternal<int32_t>(data);
1267  break;
1268 
1269  case TULONG:
1270  aggregateInternal<uint32_t>(data);
1271  break;
1272 
1273  case TFLOAT:
1274  aggregateInternal<float>(data);
1275  break;
1276 
1277  case TLONGLONG:
1278  aggregateInternal<int64_t>(data);
1279  break;
1280 
1281  case TDOUBLE:
1282  aggregateInternal<double>(data);
1283  break;
1284 
1285  default:
1286  break;
1287  }
1288 }
1289 
1290 ///////////////////////////////////////////////////////////////////////////////////////
1291 ///
1292 ///////////////////////////////////////////////////////////////////////////////////////
1293 template <typename T>
1294 void DarkLibrary::aggregateInternal(const QSharedPointer<FITSData> &data)
1295 {
1296  T const *darkBuffer = reinterpret_cast<T const*>(data->getImageBuffer());
1297  for (uint32_t i = 0; i < m_DarkMasterBuffer.size(); i++)
1298  m_DarkMasterBuffer[i] += darkBuffer[i];
1299 }
1300 
1301 ///////////////////////////////////////////////////////////////////////////////////////
1302 ///
1303 ///////////////////////////////////////////////////////////////////////////////////////
1304 void DarkLibrary::generateMasterFrame(const QSharedPointer<FITSData> &data, const QJsonObject &metadata)
1305 {
1306  switch (data->dataType())
1307  {
1308  case TBYTE:
1309  generateMasterFrameInternal<uint8_t>(data, metadata);
1310  break;
1311 
1312  case TSHORT:
1313  generateMasterFrameInternal<int16_t>(data, metadata);
1314  break;
1315 
1316  case TUSHORT:
1317  generateMasterFrameInternal<uint16_t>(data, metadata);
1318  break;
1319 
1320  case TLONG:
1321  generateMasterFrameInternal<int32_t>(data, metadata);
1322  break;
1323 
1324  case TULONG:
1325  generateMasterFrameInternal<uint32_t>(data, metadata);
1326  break;
1327 
1328  case TFLOAT:
1329  generateMasterFrameInternal<float>(data, metadata);
1330  break;
1331 
1332  case TLONGLONG:
1333  generateMasterFrameInternal<int64_t>(data, metadata);
1334  break;
1335 
1336  case TDOUBLE:
1337  generateMasterFrameInternal<double>(data, metadata);
1338  break;
1339 
1340  default:
1341  break;
1342  }
1343 
1344  emit newImage(data);
1345  // Reset Master Buffer
1346  m_DarkMasterBuffer.assign(m_DarkMasterBuffer.size(), 0);
1347 
1348 }
1349 
1350 ///////////////////////////////////////////////////////////////////////////////////////
1351 ///
1352 ///////////////////////////////////////////////////////////////////////////////////////
1353 template <typename T> void DarkLibrary::generateMasterFrameInternal(const QSharedPointer<FITSData> &data,
1354  const QJsonObject &metadata)
1355 {
1356  T *writableBuffer = reinterpret_cast<T *>(data->getWritableImageBuffer());
1357  const uint32_t count = metadata["count"].toInt();
1358  // Average the values
1359  for (uint32_t i = 0; i < m_DarkMasterBuffer.size(); i++)
1360  writableBuffer[i] = m_DarkMasterBuffer[i] / count;
1361 
1362  QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
1363  QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("darks/darkframe_" + ts +
1364  data->extension());
1365 
1366  data->calculateStats(true);
1367  if (!data->saveImage(path))
1368  {
1369  m_FileLabel->setText(i18n("Failed to save master frame: %1", data->getLastError()));
1370  return;
1371  }
1372 
1373  auto memoryMB = KSUtils::getAvailableRAM() / 1e6;
1374  if (memoryMB > CACHE_MEMORY_LIMIT)
1375  cacheDarkFrameFromFile(data->filename());
1376 
1377  QVariantMap map;
1378  map["ccd"] = metadata["camera"].toString();
1379  map["chip"] = metadata["chip"].toInt();
1380  map["binX"] = metadata["binx"].toInt();
1381  map["binY"] = metadata["biny"].toInt();
1382  map["temperature"] = metadata["temperature"].toDouble(INVALID_VALUE);
1383  map["gain"] = metadata["gain"].toInt(-1);
1384  map["iso"] = metadata["iso"].toString();
1385  map["duration"] = metadata["duration"].toDouble();
1386  map["filename"] = path;
1388 
1389  m_DarkFramesDatabaseList.append(map);
1390  m_FileLabel->setText(i18n("Master Dark saved to %1", path));
1391  KStarsData::Instance()->userdb()->AddDarkFrame(map);
1392 }
1393 
1394 ///////////////////////////////////////////////////////////////////////////////////////
1395 ///
1396 ///////////////////////////////////////////////////////////////////////////////////////
1397 void DarkLibrary::setCaptureModule(Capture *instance)
1398 {
1399  m_CaptureModule = instance;
1400 }
1401 
1402 ///////////////////////////////////////////////////////////////////////////////////////
1403 ///
1404 ///////////////////////////////////////////////////////////////////////////////////////
1405 void DarkLibrary::setCaptureState(CaptureState state)
1406 {
1407  switch (state)
1408  {
1409  case CAPTURE_ABORTED:
1410  setCompleted();
1411  m_StatusLabel->setText(i18n("Capture aborted."));
1412  break;
1413  case CAPTURE_COMPLETE:
1414  setCompleted();
1415  m_StatusLabel->setText(i18n("Capture completed."));
1416  break;
1417  default:
1418  break;
1419  }
1420 }
1421 
1422 ///////////////////////////////////////////////////////////////////////////////////////
1423 ///
1424 ///////////////////////////////////////////////////////////////////////////////////////
1425 void DarkLibrary::saveDefectMap()
1426 {
1427  if (!m_CurrentDarkFrame)
1428  return;
1429 
1430  QString filename = m_CurrentDefectMap->filename();
1431  bool newFile = false;
1432  if (filename.isEmpty())
1433  {
1434  QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
1435  filename = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("defectmaps/defectmap_" + ts +
1436  ".json");
1437  newFile = true;
1438  }
1439 
1440  if (m_CurrentDefectMap->save(filename, m_Camera->getDeviceName()))
1441  {
1442  m_FileLabel->setText(i18n("Defect map saved to %1", filename));
1443 
1444  if (newFile)
1445  {
1446  auto currentMap = std::find_if(m_DarkFramesDatabaseList.begin(),
1447  m_DarkFramesDatabaseList.end(), [&](const QVariantMap & oneMap)
1448  {
1449  return oneMap["filename"].toString() == m_CurrentDarkFrame->filename();
1450  });
1451 
1452  if (currentMap != m_DarkFramesDatabaseList.end())
1453  {
1454  (*currentMap)["defectmap"] = filename;
1455  (*currentMap)["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
1456  KStarsData::Instance()->userdb()->UpdateDarkFrame(*currentMap);
1457  }
1458  }
1459  }
1460  else
1461  {
1462  m_FileLabel->setText(i18n("Failed to save defect map to %1", filename));
1463  }
1464 }
1465 
1466 ///////////////////////////////////////////////////////////////////////////////////////
1467 ///
1468 ///////////////////////////////////////////////////////////////////////////////////////
1469 void DarkLibrary::start()
1470 {
1471  generateDarkJobs();
1472  execute();
1473 }
1474 
1475 ///////////////////////////////////////////////////////////////////////////////////////
1476 ///
1477 ///////////////////////////////////////////////////////////////////////////////////////
1478 void DarkLibrary::setCameraPresets(const QJsonObject &settings)
1479 {
1480  const auto opticalTrain = settings["optical_train"].toString();
1481  const auto isDarkPrefer = settings["isDarkPrefer"].toBool(preferDarksRadio->isChecked());
1482  const auto isDefectPrefer = settings["isDefectPrefer"].toBool(preferDefectsRadio->isChecked());
1483  opticalTrainCombo->setCurrentText(opticalTrain);
1484  preferDarksRadio->setChecked(isDarkPrefer);
1485  preferDefectsRadio->setChecked(isDefectPrefer);
1486  checkCamera();
1487  reloadDarksFromDatabase();
1488 }
1489 
1490 ///////////////////////////////////////////////////////////////////////////////////////
1491 ///
1492 ///////////////////////////////////////////////////////////////////////////////////////
1493 QJsonObject DarkLibrary::getCameraPresets()
1494 {
1495  QJsonObject cameraSettings =
1496  {
1497  {"optical_train", opticalTrainCombo->currentText()},
1498  {"preferDarksRadio", preferDarksRadio->isChecked()},
1499  {"preferDefectsRadio", preferDefectsRadio->isChecked()},
1500  {"fileName", m_FileLabel->text()}
1501  };
1502  return cameraSettings;
1503 }
1504 
1505 ///////////////////////////////////////////////////////////////////////////////////////
1506 ///
1507 ///////////////////////////////////////////////////////////////////////////////////////
1508 QJsonArray DarkLibrary::getViewMasters()
1509 {
1510  QJsonArray array;
1511 
1512  for(int i = 0; i < darkFramesModel->rowCount(); i++)
1513  {
1514  QSqlRecord record = darkFramesModel->record(i);
1515  auto camera = record.value("ccd").toString();
1516  auto binX = record.value("binX").toInt();
1517  auto binY = record.value("binY").toInt();
1518  auto temperature = record.value("temperature").toDouble();
1519  auto duration = record.value("duration").toDouble();
1520  auto ts = record.value("timestamp").toString();
1521  auto gain = record.value("gain").toInt();
1522  auto iso = record.value("iso").toString();
1523 
1524  QJsonObject filterRows =
1525  {
1526  {"camera", camera},
1527  {"binX", binX},
1528  {"binY", binY},
1529  {"temperature", temperature},
1530  {"duaration", duration},
1531  {"ts", ts}
1532  };
1533 
1534  if (gain >= 0)
1535  filterRows["gain"] = gain;
1536  if (!iso.isEmpty())
1537  filterRows["iso"] = iso;
1538 
1539  array.append(filterRows);
1540  }
1541  return array;
1542 }
1543 
1544 ///////////////////////////////////////////////////////////////////////////////////////
1545 ///
1546 ///////////////////////////////////////////////////////////////////////////////////////
1547 void DarkLibrary::setDefectPixels(const QJsonObject &payload)
1548 {
1549  const auto hotSpin = payload["hotSpin"].toInt();
1550  const auto coldSpin = payload["coldSpin"].toInt();
1551  const auto hotEnabled = payload["hotEnabled"].toBool(hotPixelsEnabled->isChecked());
1552  const auto coldEnabled = payload["coldEnabled"].toBool(coldPixelsEnabled->isChecked());
1553 
1554  hotPixelsEnabled->setChecked(hotEnabled);
1555  coldPixelsEnabled->setChecked(coldEnabled);
1556 
1557  aggresivenessHotSpin->setValue(hotSpin);
1558  aggresivenessColdSpin->setValue(coldSpin);
1559 
1560  m_DarkView->ZoomDefault();
1561 
1562  setDefectMapEnabled(true);
1563  generateMapB->click();
1564 }
1565 
1566 ///////////////////////////////////////////////////////////////////////////////////////
1567 ///
1568 ///////////////////////////////////////////////////////////////////////////////////////
1569 void DarkLibrary::setDefectMapEnabled(bool enabled)
1570 {
1571  m_DarkView->setDefectMapEnabled(enabled);
1572 }
1573 
1574 ///////////////////////////////////////////////////////////////////////////////////////
1575 ///
1576 ///////////////////////////////////////////////////////////////////////////////////////
1577 double DarkLibrary::getGain()
1578 {
1579  // Gain is manifested in two forms
1580  // Property CCD_GAIN and
1581  // Part of CCD_CONTROLS properties.
1582  // Therefore, we have to find what the currently camera supports first.
1583  auto gain = m_Camera->getProperty("CCD_GAIN");
1584  if (gain)
1585  return gain.getNumber()->at(0)->value;
1586 
1587 
1588  auto controls = m_Camera->getProperty("CCD_CONTROLS");
1589  if (controls)
1590  {
1591  auto oneGain = controls.getNumber()->findWidgetByName("Gain");
1592  if (oneGain)
1593  return oneGain->value;
1594  }
1595 
1596  return -1;
1597 }
1598 
1599 ///////////////////////////////////////////////////////////////////////////////////////
1600 ///
1601 ///////////////////////////////////////////////////////////////////////////////////////
1602 void DarkLibrary::setupOpticalTrainManager()
1603 {
1604  connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &DarkLibrary::refreshOpticalTrain);
1605  connect(trainB, &QPushButton::clicked, this, [this]()
1606  {
1607  OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
1608  });
1609  connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
1610  {
1611  ProfileSettings::Instance()->setOneSetting(ProfileSettings::DarkLibraryOpticalTrain,
1612  OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index)));
1613  refreshOpticalTrain();
1614  emit trainChanged();
1615  });
1616 }
1617 
1618 ///////////////////////////////////////////////////////////////////////////////////////
1619 ///
1620 ///////////////////////////////////////////////////////////////////////////////////////
1621 void DarkLibrary::refreshOpticalTrain()
1622 {
1623  opticalTrainCombo->blockSignals(true);
1624  opticalTrainCombo->clear();
1625  opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
1626  trainB->setEnabled(true);
1627 
1628  QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::DarkLibraryOpticalTrain);
1629 
1630  if (trainID.isValid())
1631  {
1632  auto id = trainID.toUInt();
1633 
1634  // If train not found, select the first one available.
1635  if (OpticalTrainManager::Instance()->exists(id) == false)
1636  {
1637  emit newLog(i18n("Optical train doesn't exist for id %1", id));
1638  id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
1639  }
1640 
1641  auto name = OpticalTrainManager::Instance()->name(id);
1642 
1643  opticalTrainCombo->setCurrentText(name);
1644 
1645  auto camera = OpticalTrainManager::Instance()->getCamera(name);
1646  if (camera)
1647  {
1648  auto scope = OpticalTrainManager::Instance()->getScope(name);
1649  opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(camera->getDeviceName(), scope["name"].toString()));
1650  }
1651  setCamera(camera);
1652 
1653  // Load train settings
1654  OpticalTrainSettings::Instance()->setOpticalTrainID(id);
1655  auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::DarkLibrary);
1656  if (settings.isValid())
1657  setAllSettings(settings.toJsonObject().toVariantMap());
1658  else
1659  m_Settings = m_GlobalSettings;
1660  }
1661 
1662  opticalTrainCombo->blockSignals(false);
1663 }
1664 
1665 ///////////////////////////////////////////////////////////////////////////////////////
1666 ///
1667 ///////////////////////////////////////////////////////////////////////////////////////
1668 void DarkLibrary::loadGlobalSettings()
1669 {
1670  QString key;
1671  QVariant value;
1672 
1673  QVariantMap settings;
1674  // All Combo Boxes
1675  for (auto &oneWidget : findChildren<QComboBox*>())
1676  {
1677  if (oneWidget->objectName() == "opticalTrainCombo")
1678  continue;
1679 
1680  key = oneWidget->objectName();
1681  value = Options::self()->property(key.toLatin1());
1682  if (value.isValid())
1683  {
1684  oneWidget->setCurrentText(value.toString());
1685  settings[key] = value;
1686  }
1687  }
1688 
1689  // All Double Spin Boxes
1690  for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1691  {
1692  key = oneWidget->objectName();
1693  value = Options::self()->property(key.toLatin1());
1694  if (value.isValid())
1695  {
1696  oneWidget->setValue(value.toDouble());
1697  settings[key] = value;
1698  }
1699  }
1700 
1701  // All Spin Boxes
1702  for (auto &oneWidget : findChildren<QSpinBox*>())
1703  {
1704  key = oneWidget->objectName();
1705  value = Options::self()->property(key.toLatin1());
1706  if (value.isValid())
1707  {
1708  oneWidget->setValue(value.toInt());
1709  settings[key] = value;
1710  }
1711  }
1712 
1713  // All Checkboxes
1714  for (auto &oneWidget : findChildren<QCheckBox*>())
1715  {
1716  key = oneWidget->objectName();
1717  value = Options::self()->property(key.toLatin1());
1718  if (value.isValid())
1719  {
1720  oneWidget->setChecked(value.toBool());
1721  settings[key] = value;
1722  }
1723  }
1724 
1725  m_GlobalSettings = m_Settings = settings;
1726 }
1727 
1728 
1729 ///////////////////////////////////////////////////////////////////////////////////////
1730 ///
1731 ///////////////////////////////////////////////////////////////////////////////////////
1732 void DarkLibrary::connectSettings()
1733 {
1734  // All Combo Boxes
1735  for (auto &oneWidget : findChildren<QComboBox*>())
1736  connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::DarkLibrary::syncSettings);
1737 
1738  // All Double Spin Boxes
1739  for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1740  connect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings);
1741 
1742  // All Spin Boxes
1743  for (auto &oneWidget : findChildren<QSpinBox*>())
1744  connect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings);
1745 
1746  // All Checkboxes
1747  for (auto &oneWidget : findChildren<QCheckBox*>())
1748  connect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings);
1749 
1750  // All Radio buttons
1751  for (auto &oneWidget : findChildren<QRadioButton*>())
1752  connect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings);
1753 
1754  // Train combo box should NOT be synced.
1755  disconnect(opticalTrainCombo, QOverload<int>::of(&QComboBox::activated), this, &Ekos::DarkLibrary::syncSettings);
1756 }
1757 
1758 ///////////////////////////////////////////////////////////////////////////////////////
1759 ///
1760 ///////////////////////////////////////////////////////////////////////////////////////
1761 void DarkLibrary::disconnectSettings()
1762 {
1763  // All Combo Boxes
1764  for (auto &oneWidget : findChildren<QComboBox*>())
1765  disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::DarkLibrary::syncSettings);
1766 
1767  // All Double Spin Boxes
1768  for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1769  disconnect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings);
1770 
1771  // All Spin Boxes
1772  for (auto &oneWidget : findChildren<QSpinBox*>())
1773  disconnect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings);
1774 
1775  // All Checkboxes
1776  for (auto &oneWidget : findChildren<QCheckBox*>())
1777  disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings);
1778 
1779  // All Radio buttons
1780  for (auto &oneWidget : findChildren<QRadioButton*>())
1781  disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings);
1782 
1783 }
1784 
1785 ///////////////////////////////////////////////////////////////////////////////////////////
1786 ///
1787 ///////////////////////////////////////////////////////////////////////////////////////////
1788 QVariantMap DarkLibrary::getAllSettings() const
1789 {
1790  QVariantMap settings;
1791 
1792  // All Combo Boxes
1793  for (auto &oneWidget : findChildren<QComboBox*>())
1794  settings.insert(oneWidget->objectName(), oneWidget->currentText());
1795 
1796  // All Double Spin Boxes
1797  for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1798  settings.insert(oneWidget->objectName(), oneWidget->value());
1799 
1800  // All Spin Boxes
1801  for (auto &oneWidget : findChildren<QSpinBox*>())
1802  settings.insert(oneWidget->objectName(), oneWidget->value());
1803 
1804  // All Checkboxes
1805  for (auto &oneWidget : findChildren<QCheckBox*>())
1806  settings.insert(oneWidget->objectName(), oneWidget->isChecked());
1807 
1808  return settings;
1809 }
1810 
1811 ///////////////////////////////////////////////////////////////////////////////////////////
1812 ///
1813 ///////////////////////////////////////////////////////////////////////////////////////////
1814 void DarkLibrary::setAllSettings(const QVariantMap &settings)
1815 {
1816  // Disconnect settings that we don't end up calling syncSettings while
1817  // performing the changes.
1818  disconnectSettings();
1819 
1820  for (auto &name : settings.keys())
1821  {
1822  // Combo
1823  auto comboBox = findChild<QComboBox*>(name);
1824  if (comboBox)
1825  {
1826  syncControl(settings, name, comboBox);
1827  continue;
1828  }
1829 
1830  // Double spinbox
1831  auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
1832  if (doubleSpinBox)
1833  {
1834  syncControl(settings, name, doubleSpinBox);
1835  continue;
1836  }
1837 
1838  // spinbox
1839  auto spinBox = findChild<QSpinBox*>(name);
1840  if (spinBox)
1841  {
1842  syncControl(settings, name, spinBox);
1843  continue;
1844  }
1845 
1846  // checkbox
1847  auto checkbox = findChild<QCheckBox*>(name);
1848  if (checkbox)
1849  {
1850  syncControl(settings, name, checkbox);
1851  continue;
1852  }
1853 
1854  // Radio button
1855  auto radioButton = findChild<QRadioButton*>(name);
1856  if (radioButton)
1857  {
1858  syncControl(settings, name, radioButton);
1859  continue;
1860  }
1861  }
1862 
1863  // Sync to options
1864  for (auto &key : settings.keys())
1865  {
1866  auto value = settings[key];
1867  // Save immediately
1868  Options::self()->setProperty(key.toLatin1(), value);
1869  Options::self()->save();
1870 
1871  m_Settings[key] = value;
1872  m_GlobalSettings[key] = value;
1873  }
1874 
1875  emit settingsUpdated(getAllSettings());
1876 
1877  // Save to optical train specific settings as well
1878  OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
1879  OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::DarkLibrary, m_Settings);
1880 
1881  // Restablish connections
1882  connectSettings();
1883 }
1884 
1885 ///////////////////////////////////////////////////////////////////////////////////////////
1886 ///
1887 ///////////////////////////////////////////////////////////////////////////////////////////
1888 bool DarkLibrary::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget)
1889 {
1890  QSpinBox *pSB = nullptr;
1891  QDoubleSpinBox *pDSB = nullptr;
1892  QCheckBox *pCB = nullptr;
1893  QComboBox *pComboBox = nullptr;
1894  QRadioButton *pRadioButton = nullptr;
1895  bool ok = false;
1896 
1897  if ((pSB = qobject_cast<QSpinBox *>(widget)))
1898  {
1899  const int value = settings[key].toInt(&ok);
1900  if (ok)
1901  {
1902  pSB->setValue(value);
1903  return true;
1904  }
1905  }
1906  else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
1907  {
1908  const double value = settings[key].toDouble(&ok);
1909  if (ok)
1910  {
1911  pDSB->setValue(value);
1912  return true;
1913  }
1914  }
1915  else if ((pCB = qobject_cast<QCheckBox *>(widget)))
1916  {
1917  const bool value = settings[key].toBool();
1918  pCB->setChecked(value);
1919  return true;
1920  }
1921  // ONLY FOR STRINGS, not INDEX
1922  else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
1923  {
1924  const QString value = settings[key].toString();
1925  pComboBox->setCurrentText(value);
1926  return true;
1927  }
1928  else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
1929  {
1930  const bool value = settings[key].toBool();
1931  pRadioButton->setChecked(value);
1932  return true;
1933  }
1934 
1935  return false;
1936 };
1937 
1938 ///////////////////////////////////////////////////////////////////////////////////////
1939 ///
1940 ///////////////////////////////////////////////////////////////////////////////////////
1941 void DarkLibrary::syncSettings()
1942 {
1943  QDoubleSpinBox *dsb = nullptr;
1944  QSpinBox *sb = nullptr;
1945  QCheckBox *cb = nullptr;
1946  QComboBox *cbox = nullptr;
1947  QRadioButton *cradio = nullptr;
1948 
1949  QString key;
1950  QVariant value;
1951 
1952  if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
1953  {
1954  key = dsb->objectName();
1955  value = dsb->value();
1956 
1957  }
1958  else if ( (sb = qobject_cast<QSpinBox*>(sender())))
1959  {
1960  key = sb->objectName();
1961  value = sb->value();
1962  }
1963  else if ( (cb = qobject_cast<QCheckBox*>(sender())))
1964  {
1965  key = cb->objectName();
1966  value = cb->isChecked();
1967  }
1968  else if ( (cbox = qobject_cast<QComboBox*>(sender())))
1969  {
1970  key = cbox->objectName();
1971  value = cbox->currentText();
1972  }
1973  else if ( (cradio = qobject_cast<QRadioButton*>(sender())))
1974  {
1975  key = cradio->objectName();
1976 
1977  // N.B. Only store CHECKED radios, do not store unchecked ones
1978  // as we only have exclusive groups.
1979  if (cradio->isChecked() == false)
1980  {
1981  // Remove from setting if it was added before
1982  if (m_Settings.contains(key))
1983  {
1984  m_Settings.remove(key);
1985  emit settingsUpdated(getAllSettings());
1986  OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
1987  OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::DarkLibrary, m_Settings);
1988  }
1989  return;
1990  }
1991 
1992  value = true;
1993  }
1994 
1995  // Save immediately
1996  Options::self()->setProperty(key.toLatin1(), value);
1997  Options::self()->save();
1998 
1999  m_Settings[key] = value;
2000  m_GlobalSettings[key] = value;
2001 
2002  emit settingsUpdated(getAllSettings());
2003 
2004  // Save to optical train specific settings as well
2005  OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
2006  OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::DarkLibrary, m_Settings);
2007 }
2008 
2009 ///////////////////////////////////////////////////////////////////////////////////////
2010 ///
2011 ///////////////////////////////////////////////////////////////////////////////////////
2012 QJsonObject DarkLibrary::getDefectSettings()
2013 {
2014  QStringList darkMasters;
2015  for (int i = 0; i < masterDarksCombo->count(); i++)
2016  darkMasters << masterDarksCombo->itemText(i);
2017 
2018  QJsonObject createDefectMaps =
2019  {
2020  {"masterTime", masterTime->text()},
2021  {"masterDarks", darkMasters.join('|')},
2022  {"masterExposure", masterExposure->text()},
2023  {"masterTempreture", masterTemperature->text()},
2024  {"masterMean", masterMean->text()},
2025  {"masterMedian", masterMedian->text()},
2026  {"masterDeviation", masterDeviation->text()},
2027  {"hotPixelsEnabled", hotPixelsEnabled->isChecked()},
2028  {"coldPixelsEnabled", coldPixelsEnabled->isChecked()},
2029  };
2030  return createDefectMaps;
2031 }
2032 
2033 
2034 
2035 }
2036 
@ CAPTURE_COMPLETE
Definition: ekos.h:112
void setCurrentText(const QString &text)
AlignRight
void doubleClicked(const QModelIndex &index)
bool isValid() const const
QString number(int n, int base)
QByteArray fromRawData(const char *data, int size)
bool remove()
Ekos is an advanced Astrophotography tool for Linux. It is based on a modular extensible framework to...
Definition: align.cpp:69
QDateTime currentDateTime()
QChar separator()
QVariantMap toVariantMap() const const
QDateTime addDays(qint64 ndays) const const
void clicked(bool checked)
KSUserDB * userdb()
Definition: kstarsdata.h:215
QByteArray toLatin1() const const
bool openUrl(const QUrl &url)
void setValue(double val)
void setChecked(bool)
bool DeleteDarkFrame(const QString &filename)
KSUserDB::DeleteDarkFrame Delete from database a dark frame record that matches the filename field.
Definition: ksuserdb.cpp:771
QVariant value(int index) const const
void setValue(const T &value)
qint64 currentSecsSinceEpoch()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void toggled(bool checked)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
static KStars * Instance()
Definition: kstars.h:123
QString tempPath()
bool UpdateDarkFrame(const QVariantMap &oneFrame)
KSUserDB::UpdateDarkFrame Updates an existing dark frame record in the data, replace all values match...
Definition: ksuserdb.cpp:743
void deleteLater()
Sequence Job is a container for the details required to capture a series of images.
Definition: sequencejob.h:18
double toDouble(bool *ok) const const
QString i18n(const char *text, const TYPE &arg...)
bool isEmpty() const const
QUrl fromLocalFile(const QString &localFile)
void currentChanged(int index)
uint toUInt(bool *ok) const const
int toInt(bool *ok) const const
UniqueConnection
bool isEmpty() const const
QJsonValue value(const QString &key) const const
QString join(const QString &separator) const const
void setupUi(QWidget *widget)
void waitForFinished()
bool toBool() const const
@ CAPTURE_ABORTED
Definition: ekos.h:99
int row() const const
qint64 daysTo(const QDateTime &other) const const
void buttonToggled(QAbstractButton *button, bool checked)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString path(const QString &relativePath)
void editingFinished()
void setValue(int val)
QString name(StandardShortcut id)
QString filePath(const QString &fileName) const const
void currentIndexChanged(int index)
void clear()
void valueChanged(int value)
void idToggled(int id, bool checked)
void append(const QJsonValue &value)
QString toString(Qt::DateFormat format) const const
QSqlDatabase database(const QString &connectionName, bool open)
ButtonCode questionYesNo(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonYes=KStandardGuiItem::yes(), const KGuiItem &buttonNo=KStandardGuiItem::no(), const QString &dontAskAgainName=QString(), Options options=Notify)
QFuture< void > map(Sequence &sequence, MapFunctor function)
void activated(int index)
QString & append(QChar ch)
T result() const const
bool AddDarkFrame(const QVariantMap &oneFrame)
KSUserDB::AddDarkFrame Saves a new dark frame data to the database.
Definition: ksuserdb.cpp:714
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Fri Sep 29 2023 03:56:53 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.