Kstars

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

KDE's Doxygen guidelines are available online.