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 darkFramesModel->removeRow(i);
683
684 }
685
686 darkFramesModel->submitAll();
687
688 // Refesh db entries for other cameras
689 refreshFromDB();
690 reloadDarksFromDatabase();
691}
692
693///////////////////////////////////////////////////////////////////////////////////////
694///
695///////////////////////////////////////////////////////////////////////////////////////
696void DarkLibrary::clearRow(int index)
697{
698 if (index < 0)
699 return;
700
701 QSqlRecord record = darkFramesModel->record(index);
702 QString filename = record.value("filename").toString();
703 QString defectMap = record.value("defectmap").toString();
704 QFile::remove(filename);
705 if (!defectMap.isEmpty())
706 QFile::remove(defectMap);
707
708 KStarsData::Instance()->userdb()->DeleteDarkFrame(filename);
709 refreshFromDB();
710 reloadDarksFromDatabase();
711}
712
713///////////////////////////////////////////////////////////////////////////////////////
714///
715///////////////////////////////////////////////////////////////////////////////////////
716void DarkLibrary::openDarksFolder()
717{
718 QString darkFilesPath = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("darks");
719
721}
722
723///////////////////////////////////////////////////////////////////////////////////////
724///
725///////////////////////////////////////////////////////////////////////////////////////
726void DarkLibrary::refreshDefectMastersList(const QString &camera)
727{
728 if (darkFramesModel->rowCount() == 0)
729 return;
730
731 masterDarksCombo->blockSignals(true);
732 masterDarksCombo->clear();
733
734 for (int i = 0; i < darkFramesModel->rowCount(); ++i)
735 {
736 QSqlRecord record = darkFramesModel->record(i);
737
738 if (record.value("ccd") != camera)
739 continue;
740
741 auto binX = record.value("binX").toInt();
742 auto binY = record.value("binY").toInt();
743 auto temperature = record.value("temperature").toDouble();
744 auto duration = record.value("duration").toDouble();
745 auto gain = record.value("gain").toInt();
746 auto iso = record.value("iso").toString();
747 QString ts = record.value("timestamp").toString();
748
749 QString entry = QString("%1 secs %2x%3")
750 .arg(QString::number(duration, 'f', 1))
751 .arg(QString::number(binX))
752 .arg(QString::number(binY));
753
754 if (temperature > INVALID_VALUE)
755 entry.append(QString(" @ %1°").arg(QString::number(temperature, 'f', 1)));
756
757 if (gain >= 0)
758 entry.append(QString(" G %1").arg(gain));
759 if (!iso.isEmpty())
760 entry.append(QString(" ISO %1").arg(iso));
761
762 masterDarksCombo->addItem(entry);
763 }
764
765 masterDarksCombo->blockSignals(false);
766
767 //loadDefectMap();
768
769}
770///////////////////////////////////////////////////////////////////////////////////////
771///
772///////////////////////////////////////////////////////////////////////////////////////
773void DarkLibrary::reloadDarksFromDatabase()
774{
775 if (!m_Camera) return;
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///////////////////////////////////////////////////////////////////////////////////////
812void 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() || !QFileInfo::exists(m_MasterDarkFrameFilename))
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///////////////////////////////////////////////////////////////////////////////////////
847void 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///////////////////////////////////////////////////////////////////////////////////////
882void 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///////////////////////////////////////////////////////////////////////////////////////
921void 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///////////////////////////////////////////////////////////////////////////////////////
933bool 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 // JM 2024.03.09: Add a bandaid for a mysteroius crash that sometimes happen
948 // when loading dark frame on Ekos startup. The crash occurs in cfitsio
949 // Hopefully this delay might fix it
950 QTimer::singleShot(1000, this, &DarkLibrary::reloadDarksFromDatabase);
951 return true;
952 }
953 else
954 {
955 darkTabsWidget->setEnabled(false);
956 return false;
957 }
958}
959
960///////////////////////////////////////////////////////////////////////////////////////
961///
962///////////////////////////////////////////////////////////////////////////////////////
963void DarkLibrary::removeDevice(const QSharedPointer<ISD::GenericDevice> &device)
964{
965 if (m_Camera && m_Camera->getDeviceName() == device->getDeviceName())
966 {
967 m_Camera->disconnect(this);
968 m_Camera = nullptr;
969 }
970}
971
972///////////////////////////////////////////////////////////////////////////////////////
973///
974///////////////////////////////////////////////////////////////////////////////////////
975void DarkLibrary::checkCamera()
976{
977 if (!m_Camera)
978 return;
979
980 auto device = m_Camera->getDeviceName();
981
982 m_TargetChip = nullptr;
983 // FIXME TODO
984 // Need to figure guide head
985 if (device.contains("Guider"))
986 {
987 m_UseGuideHead = true;
988 m_TargetChip = m_Camera->getChip(ISD::CameraChip::GUIDE_CCD);
989 }
990
991 if (m_TargetChip == nullptr)
992 {
993 m_UseGuideHead = false;
994 m_TargetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD);
995 }
996
997 // Make sure we have a valid chip and valid base device.
998 // Make sure we are not in capture process.
999 if (!m_TargetChip || !m_TargetChip->getCCD() || m_TargetChip->isCapturing())
1000 return;
1001
1002 if (m_Camera->hasCoolerControl())
1003 {
1004 temperatureLabel->setEnabled(true);
1005 temperatureStepLabel->setEnabled(true);
1006 temperatureToLabel->setEnabled(true);
1007 temperatureStepSpin->setEnabled(true);
1008 minTemperatureSpin->setEnabled(true);
1009 maxTemperatureSpin->setEnabled(true);
1010
1011 // Get default temperature
1012 double temperature = 0;
1013 // Update if no setting was previously set
1014 if (m_Camera->getTemperature(&temperature))
1015 {
1016 minTemperatureSpin->setValue(temperature);
1017 maxTemperatureSpin->setValue(temperature);
1018 }
1019
1020 }
1021 else
1022 {
1023 temperatureLabel->setEnabled(false);
1024 temperatureStepLabel->setEnabled(false);
1025 temperatureToLabel->setEnabled(false);
1026 temperatureStepSpin->setEnabled(false);
1027 minTemperatureSpin->setEnabled(false);
1028 maxTemperatureSpin->setEnabled(false);
1029 }
1030
1031 QStringList isoList = m_TargetChip->getISOList();
1032 captureISOS->blockSignals(true);
1033 captureISOS->clear();
1034
1035 // No ISO range available
1036 if (isoList.isEmpty())
1037 {
1038 captureISOS->setEnabled(false);
1039 }
1040 else
1041 {
1042 captureISOS->setEnabled(true);
1043 captureISOS->addItems(isoList);
1044 captureISOS->setCurrentIndex(m_TargetChip->getISOIndex());
1045 }
1046 captureISOS->blockSignals(false);
1047
1048 // Gain Check
1049 if (m_Camera->hasGain())
1050 {
1051 double min, max, step, value, targetCustomGain;
1052 m_Camera->getGainMinMaxStep(&min, &max, &step);
1053
1054 // Allow the possibility of no gain value at all.
1055 GainSpinSpecialValue = min - step;
1056 captureGainN->setRange(GainSpinSpecialValue, max);
1057 captureGainN->setSpecialValueText(i18n("--"));
1058 captureGainN->setEnabled(true);
1059 captureGainN->setSingleStep(step);
1060 m_Camera->getGain(&value);
1061
1062 targetCustomGain = getGain();
1063
1064 // Set the custom gain if we have one
1065 // otherwise it will not have an effect.
1066 if (targetCustomGain > 0)
1067 captureGainN->setValue(targetCustomGain);
1068 else
1069 captureGainN->setValue(GainSpinSpecialValue);
1070
1071 captureGainN->setReadOnly(m_Camera->getGainPermission() == IP_RO);
1072 }
1073 else
1074 captureGainN->setEnabled(false);
1075
1076 countDarkTotalTime();
1077
1078}
1079
1080///////////////////////////////////////////////////////////////////////////////////////
1081///
1082///////////////////////////////////////////////////////////////////////////////////////
1083void DarkLibrary::countDarkTotalTime()
1084{
1085 double temperatureCount = 1;
1086 if (m_Camera && m_Camera->hasCoolerControl() && std::abs(maxTemperatureSpin->value() - minTemperatureSpin->value()) > 0)
1087 temperatureCount = (std::abs((maxTemperatureSpin->value() - minTemperatureSpin->value())) / temperatureStepSpin->value()) +
1088 1;
1089 int binnings = 0;
1090 if (bin1Check->isChecked())
1091 binnings++;
1092 if (bin2Check->isChecked())
1093 binnings++;
1094 if (bin4Check->isChecked())
1095 binnings++;
1096
1097 double darkTime = 0;
1098 int imagesCount = 0;
1099 for (double startExposure = minExposureSpin->value(); startExposure <= maxExposureSpin->value();
1100 startExposure += exposureStepSin->value())
1101 {
1102 darkTime += startExposure * temperatureCount * binnings * countSpin->value();
1103 imagesCount += temperatureCount * binnings * countSpin->value();
1104 }
1105
1106 totalTime->setText(QString::number(darkTime / 60.0, 'f', 1));
1107 totalImages->setText(QString::number(imagesCount));
1108 darkProgress->setMaximum(imagesCount);
1109
1110}
1111
1112///////////////////////////////////////////////////////////////////////////////////////
1113///
1114///////////////////////////////////////////////////////////////////////////////////////
1115void DarkLibrary::generateDarkJobs()
1116{
1117 // Always clear sequence queue before starting
1118 m_CaptureModule->clearSequenceQueue();
1119
1120 if (m_JobsGenerated == false)
1121 {
1122 m_JobsGenerated = true;
1123 m_CaptureModuleSettings = m_CaptureModule->mainCamera()->getAllSettings();
1124 }
1125
1126 QList<double> temperatures;
1127 if (m_Camera->hasCoolerControl() && std::fabs(maxTemperatureSpin->value() - minTemperatureSpin->value()) >= 0)
1128 {
1129 for (double oneTemperature = minTemperatureSpin->value(); oneTemperature <= maxTemperatureSpin->value();
1130 oneTemperature += temperatureStepSpin->value())
1131 {
1132 temperatures << oneTemperature;
1133 }
1134
1135 // Enforce temperature set
1136 m_CaptureModule->mainCamera()->setForceTemperature(true);
1137 }
1138 else
1139 {
1140 // Disable temperature set
1141 m_CaptureModule->mainCamera()->setForceTemperature(false);
1142 temperatures << INVALID_VALUE;
1143 }
1144
1145 QList<uint8_t> bins;
1146 if (bin1Check->isChecked())
1147 bins << 1;
1148 if (bin2Check->isChecked())
1149 bins << 2;
1150 if (bin4Check->isChecked())
1151 bins << 4;
1152
1153 QList<double> exposures;
1154 for (double oneExposure = minExposureSpin->value(); oneExposure <= maxExposureSpin->value();
1155 oneExposure += exposureStepSin->value())
1156 {
1157 exposures << oneExposure;
1158 }
1159
1162
1163
1164 int sequence = 0;
1165 for (auto &oneTemperature : temperatures)
1166 {
1167 for (auto &oneExposure : exposures)
1168 {
1169 for (auto &oneBin : bins)
1170 {
1171 sequence++;
1172 QVariantMap settings;
1173
1174 settings["opticalTrainCombo"] = opticalTrainCombo->currentText();
1175 settings["captureExposureN"] = oneExposure;
1176 settings["captureBinHN"] = oneBin;
1177 settings["captureBinVN"] = oneBin;
1178 settings["captureTypeS"] = "Dark";
1179 settings["cameraTemperatureN"] = oneTemperature;
1180 if (captureGainN->isEnabled())
1181 settings["captureGainN"] = captureGainN->value();
1182 if (captureISOS->isEnabled())
1183 settings["captureISOS"] = captureISOS->currentText();
1184
1185 settings["fileDirectoryT"] = QString(prefix + QString("sequence_%1").arg(sequence));
1186 settings["captureCountN"] = countSpin->value();
1187
1188 m_CaptureModule->mainCamera()->setAllSettings(settings);
1189 m_CaptureModule->mainCamera()->createJob();
1190 }
1191 }
1192 }
1193}
1194
1195///////////////////////////////////////////////////////////////////////////////////////
1196///
1197///////////////////////////////////////////////////////////////////////////////////////
1198void DarkLibrary::execute()
1199{
1200 m_DarkImagesCounter = 0;
1201 darkProgress->setValue(0);
1202 darkProgress->setTextVisible(true);
1203 connect(m_CaptureModule, &Capture::newImage, this, &DarkLibrary::processNewImage, Qt::UniqueConnection);
1204 connect(m_CaptureModule, &Capture::newStatus, this, &DarkLibrary::setCaptureState, Qt::UniqueConnection);
1205 connect(m_Camera, &ISD::Camera::propertyUpdated, this, &DarkLibrary::updateProperty, Qt::UniqueConnection);
1206
1207 Options::setUseFITSViewer(false);
1208 Options::setUseSummaryPreview(false);
1209 startB->setEnabled(false);
1210 stopB->setEnabled(true);
1211 m_DarkView->reset();
1212 m_StatusLabel->setText(i18n("In progress..."));
1213 m_CaptureModule->start();
1214
1215}
1216
1217///////////////////////////////////////////////////////////////////////////////////////
1218///
1219///////////////////////////////////////////////////////////////////////////////////////
1220void DarkLibrary::stop()
1221{
1222 m_CaptureModule->abort();
1223 darkProgress->setValue(0);
1224 m_DarkView->reset();
1225}
1226
1227///////////////////////////////////////////////////////////////////////////////////////
1228///
1229///////////////////////////////////////////////////////////////////////////////////////
1230void DarkLibrary::initView()
1231{
1232 m_DarkView.reset(new DarkView(darkWidget));
1233 m_DarkView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1234 m_DarkView->setBaseSize(darkWidget->size());
1235 m_DarkView->createFloatingToolBar();
1236 QVBoxLayout *vlayout = new QVBoxLayout();
1237 vlayout->addWidget(m_DarkView.get());
1238 darkWidget->setLayout(vlayout);
1239 connect(m_DarkView.get(), &DarkView::loaded, this, [this]()
1240 {
1241 emit newImage(m_DarkView->imageData());
1242 });
1243}
1244
1245///////////////////////////////////////////////////////////////////////////////////////
1246///
1247///////////////////////////////////////////////////////////////////////////////////////
1248void DarkLibrary::aggregate(const QSharedPointer<FITSData> &data)
1249{
1250 switch (data->dataType())
1251 {
1252 case TBYTE:
1253 aggregateInternal<uint8_t>(data);
1254 break;
1255
1256 case TSHORT:
1257 aggregateInternal<int16_t>(data);
1258 break;
1259
1260 case TUSHORT:
1261 aggregateInternal<uint16_t>(data);
1262 break;
1263
1264 case TLONG:
1265 aggregateInternal<int32_t>(data);
1266 break;
1267
1268 case TULONG:
1269 aggregateInternal<uint32_t>(data);
1270 break;
1271
1272 case TFLOAT:
1273 aggregateInternal<float>(data);
1274 break;
1275
1276 case TLONGLONG:
1277 aggregateInternal<int64_t>(data);
1278 break;
1279
1280 case TDOUBLE:
1281 aggregateInternal<double>(data);
1282 break;
1283
1284 default:
1285 break;
1286 }
1287}
1288
1289///////////////////////////////////////////////////////////////////////////////////////
1290///
1291///////////////////////////////////////////////////////////////////////////////////////
1292template <typename T>
1293void DarkLibrary::aggregateInternal(const QSharedPointer<FITSData> &data)
1294{
1295 T const *darkBuffer = reinterpret_cast<T const*>(data->getImageBuffer());
1296 for (uint32_t i = 0; i < m_DarkMasterBuffer.size(); i++)
1297 m_DarkMasterBuffer[i] += darkBuffer[i];
1298}
1299
1300///////////////////////////////////////////////////////////////////////////////////////
1301///
1302///////////////////////////////////////////////////////////////////////////////////////
1303void DarkLibrary::generateMasterFrame(const QSharedPointer<FITSData> &data, const QJsonObject &metadata)
1304{
1305 switch (data->dataType())
1306 {
1307 case TBYTE:
1308 generateMasterFrameInternal<uint8_t>(data, metadata);
1309 break;
1310
1311 case TSHORT:
1312 generateMasterFrameInternal<int16_t>(data, metadata);
1313 break;
1314
1315 case TUSHORT:
1316 generateMasterFrameInternal<uint16_t>(data, metadata);
1317 break;
1318
1319 case TLONG:
1320 generateMasterFrameInternal<int32_t>(data, metadata);
1321 break;
1322
1323 case TULONG:
1324 generateMasterFrameInternal<uint32_t>(data, metadata);
1325 break;
1326
1327 case TFLOAT:
1328 generateMasterFrameInternal<float>(data, metadata);
1329 break;
1330
1331 case TLONGLONG:
1332 generateMasterFrameInternal<int64_t>(data, metadata);
1333 break;
1334
1335 case TDOUBLE:
1336 generateMasterFrameInternal<double>(data, metadata);
1337 break;
1338
1339 default:
1340 break;
1341 }
1342
1343 emit newImage(data);
1344 // Reset Master Buffer
1345 m_DarkMasterBuffer.assign(m_DarkMasterBuffer.size(), 0);
1346
1347}
1348
1349///////////////////////////////////////////////////////////////////////////////////////
1350///
1351///////////////////////////////////////////////////////////////////////////////////////
1352template <typename T> void DarkLibrary::generateMasterFrameInternal(const QSharedPointer<FITSData> &data,
1353 const QJsonObject &metadata)
1354{
1355 T *writableBuffer = reinterpret_cast<T *>(data->getWritableImageBuffer());
1356 const uint32_t count = metadata["count"].toInt();
1357 // Average the values
1358 for (uint32_t i = 0; i < m_DarkMasterBuffer.size(); i++)
1359 writableBuffer[i] = m_DarkMasterBuffer[i] / count;
1360
1361 QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
1362 QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("darks/darkframe_" + ts +
1363 data->extension());
1364
1365 data->calculateStats(true);
1366 if (!data->saveImage(path))
1367 {
1368 m_FileLabel->setText(i18n("Failed to save master frame: %1", data->getLastError()));
1369 return;
1370 }
1371
1372 auto memoryMB = KSUtils::getAvailableRAM() / 1e6;
1373 if (memoryMB > CACHE_MEMORY_LIMIT)
1374 cacheDarkFrameFromFile(data->filename());
1375
1376 QVariantMap map;
1377 map["ccd"] = metadata["camera"].toString();
1378 map["chip"] = metadata["chip"].toInt();
1379 map["binX"] = metadata["binx"].toInt();
1380 map["binY"] = metadata["biny"].toInt();
1381 map["temperature"] = metadata["temperature"].toDouble(INVALID_VALUE);
1382 map["gain"] = metadata["gain"].toInt(-1);
1383 map["iso"] = metadata["iso"].toString();
1384 map["duration"] = metadata["duration"].toDouble();
1385 map["filename"] = path;
1387
1388 m_DarkFramesDatabaseList.append(map);
1389 m_FileLabel->setText(i18n("Master Dark saved to %1", path));
1390 KStarsData::Instance()->userdb()->AddDarkFrame(map);
1391}
1392
1393///////////////////////////////////////////////////////////////////////////////////////
1394///
1395///////////////////////////////////////////////////////////////////////////////////////
1396void DarkLibrary::setCaptureModule(Capture *instance)
1397{
1398 m_CaptureModule = instance;
1399}
1400
1401///////////////////////////////////////////////////////////////////////////////////////
1402///
1403///////////////////////////////////////////////////////////////////////////////////////
1404void DarkLibrary::setCaptureState(CaptureState state)
1405{
1406 switch (state)
1407 {
1408 case CAPTURE_ABORTED:
1409 setCompleted();
1410 m_StatusLabel->setText(i18n("Capture aborted."));
1411 break;
1412 case CAPTURE_COMPLETE:
1413 setCompleted();
1414 m_StatusLabel->setText(i18n("Capture completed."));
1415 break;
1416 default:
1417 break;
1418 }
1419}
1420
1421///////////////////////////////////////////////////////////////////////////////////////
1422///
1423///////////////////////////////////////////////////////////////////////////////////////
1424void DarkLibrary::saveDefectMap()
1425{
1426 if (!m_CurrentDarkFrame || !m_CurrentDefectMap)
1427 return;
1428
1429 QString filename = m_CurrentDefectMap->filename();
1430 bool newFile = false;
1431 if (filename.isEmpty())
1432 {
1433 QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
1434 filename = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("defectmaps/defectmap_" + ts +
1435 ".json");
1436 newFile = true;
1437 }
1438
1439 if (m_CurrentDefectMap->save(filename, m_Camera->getDeviceName()))
1440 {
1441 m_FileLabel->setText(i18n("Defect map saved to %1", filename));
1442
1443 if (newFile)
1444 {
1445 auto currentMap = std::find_if(m_DarkFramesDatabaseList.begin(),
1446 m_DarkFramesDatabaseList.end(), [&](const QVariantMap & oneMap)
1447 {
1448 return oneMap["filename"].toString() == m_CurrentDarkFrame->filename();
1449 });
1450
1451 if (currentMap != m_DarkFramesDatabaseList.end())
1452 {
1453 (*currentMap)["defectmap"] = filename;
1454 (*currentMap)["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
1455 KStarsData::Instance()->userdb()->UpdateDarkFrame(*currentMap);
1456 }
1457 }
1458 }
1459 else
1460 {
1461 m_FileLabel->setText(i18n("Failed to save defect map to %1", filename));
1462 }
1463}
1464
1465///////////////////////////////////////////////////////////////////////////////////////
1466///
1467///////////////////////////////////////////////////////////////////////////////////////
1468void DarkLibrary::start()
1469{
1470 generateDarkJobs();
1471 execute();
1472}
1473
1474///////////////////////////////////////////////////////////////////////////////////////
1475///
1476///////////////////////////////////////////////////////////////////////////////////////
1477void DarkLibrary::setCameraPresets(const QJsonObject &settings)
1478{
1479 const auto opticalTrain = settings["optical_train"].toString();
1480 const auto isDarkPrefer = settings["isDarkPrefer"].toBool(preferDarksRadio->isChecked());
1481 const auto isDefectPrefer = settings["isDefectPrefer"].toBool(preferDefectsRadio->isChecked());
1482 opticalTrainCombo->setCurrentText(opticalTrain);
1483 preferDarksRadio->setChecked(isDarkPrefer);
1484 preferDefectsRadio->setChecked(isDefectPrefer);
1485 checkCamera();
1486 reloadDarksFromDatabase();
1487}
1488
1489///////////////////////////////////////////////////////////////////////////////////////
1490///
1491///////////////////////////////////////////////////////////////////////////////////////
1492QJsonObject DarkLibrary::getCameraPresets()
1493{
1494 QJsonObject cameraSettings =
1495 {
1496 {"optical_train", opticalTrainCombo->currentText()},
1497 {"preferDarksRadio", preferDarksRadio->isChecked()},
1498 {"preferDefectsRadio", preferDefectsRadio->isChecked()},
1499 {"fileName", m_FileLabel->text()}
1500 };
1501 return cameraSettings;
1502}
1503
1504///////////////////////////////////////////////////////////////////////////////////////
1505///
1506///////////////////////////////////////////////////////////////////////////////////////
1507QJsonArray DarkLibrary::getViewMasters()
1508{
1509 QJsonArray array;
1510
1511 for(int i = 0; i < darkFramesModel->rowCount(); i++)
1512 {
1513 QSqlRecord record = darkFramesModel->record(i);
1514 auto camera = record.value("ccd").toString();
1515 auto binX = record.value("binX").toInt();
1516 auto binY = record.value("binY").toInt();
1517 auto temperature = record.value("temperature").toDouble();
1518 auto duration = record.value("duration").toDouble();
1519 auto ts = record.value("timestamp").toString();
1520 auto gain = record.value("gain").toInt();
1521 auto iso = record.value("iso").toString();
1522
1523 QJsonObject filterRows =
1524 {
1525 {"camera", camera},
1526 {"binX", binX},
1527 {"binY", binY},
1528 {"temperature", temperature},
1529 {"duaration", duration},
1530 {"ts", ts}
1531 };
1532
1533 if (gain >= 0)
1534 filterRows["gain"] = gain;
1535 if (!iso.isEmpty())
1536 filterRows["iso"] = iso;
1537
1538 array.append(filterRows);
1539 }
1540 return array;
1541}
1542
1543///////////////////////////////////////////////////////////////////////////////////////
1544///
1545///////////////////////////////////////////////////////////////////////////////////////
1546void DarkLibrary::setDefectPixels(const QJsonObject &payload)
1547{
1548 const auto hotSpin = payload["hotSpin"].toInt();
1549 const auto coldSpin = payload["coldSpin"].toInt();
1550 const auto hotEnabled = payload["hotEnabled"].toBool(hotPixelsEnabled->isChecked());
1551 const auto coldEnabled = payload["coldEnabled"].toBool(coldPixelsEnabled->isChecked());
1552
1553 hotPixelsEnabled->setChecked(hotEnabled);
1554 coldPixelsEnabled->setChecked(coldEnabled);
1555
1556 aggresivenessHotSpin->setValue(hotSpin);
1557 aggresivenessColdSpin->setValue(coldSpin);
1558
1559 m_DarkView->ZoomDefault();
1560
1561 setDefectMapEnabled(true);
1562 generateMapB->click();
1563}
1564
1565///////////////////////////////////////////////////////////////////////////////////////
1566///
1567///////////////////////////////////////////////////////////////////////////////////////
1568void DarkLibrary::setDefectMapEnabled(bool enabled)
1569{
1570 m_DarkView->setDefectMapEnabled(enabled);
1571}
1572
1573///////////////////////////////////////////////////////////////////////////////////////
1574///
1575///////////////////////////////////////////////////////////////////////////////////////
1576double DarkLibrary::getGain()
1577{
1578 // Gain is manifested in two forms
1579 // Property CCD_GAIN and
1580 // Part of CCD_CONTROLS properties.
1581 // Therefore, we have to find what the currently camera supports first.
1582 auto gain = m_Camera->getProperty("CCD_GAIN");
1583 if (gain)
1584 return gain.getNumber()->at(0)->value;
1585
1586
1587 auto controls = m_Camera->getProperty("CCD_CONTROLS");
1588 if (controls)
1589 {
1590 auto oneGain = controls.getNumber()->findWidgetByName("Gain");
1591 if (oneGain)
1592 return oneGain->value;
1593 }
1594
1595 return -1;
1596}
1597
1598///////////////////////////////////////////////////////////////////////////////////////
1599///
1600///////////////////////////////////////////////////////////////////////////////////////
1601void DarkLibrary::setupOpticalTrainManager()
1602{
1603 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &DarkLibrary::refreshOpticalTrain);
1604 connect(trainB, &QPushButton::clicked, this, [this]()
1605 {
1606 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText());
1607 });
1608 connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index)
1609 {
1610 ProfileSettings::Instance()->setOneSetting(ProfileSettings::DarkLibraryOpticalTrain,
1611 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index)));
1612 refreshOpticalTrain();
1613 emit trainChanged();
1614 });
1615}
1616
1617///////////////////////////////////////////////////////////////////////////////////////
1618///
1619///////////////////////////////////////////////////////////////////////////////////////
1620void DarkLibrary::refreshOpticalTrain()
1621{
1622 opticalTrainCombo->blockSignals(true);
1623 opticalTrainCombo->clear();
1624 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames());
1625 trainB->setEnabled(true);
1626
1627 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::DarkLibraryOpticalTrain);
1628
1629 if (trainID.isValid())
1630 {
1631 auto id = trainID.toUInt();
1632
1633 // If train not found, select the first one available.
1634 if (OpticalTrainManager::Instance()->exists(id) == false)
1635 {
1636 emit newLog(i18n("Optical train doesn't exist for id %1", id));
1637 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0));
1638 }
1639
1640 auto name = OpticalTrainManager::Instance()->name(id);
1641
1642 opticalTrainCombo->setCurrentText(name);
1643
1644 auto camera = OpticalTrainManager::Instance()->getCamera(name);
1645 if (camera)
1646 {
1647 auto scope = OpticalTrainManager::Instance()->getScope(name);
1648 opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(camera->getDeviceName(), scope["name"].toString()));
1649 }
1650 setCamera(camera);
1651
1652 // Load train settings
1653 OpticalTrainSettings::Instance()->setOpticalTrainID(id);
1654 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::DarkLibrary);
1655 if (settings.isValid())
1656 {
1657 auto map = settings.toJsonObject().toVariantMap();
1658 if (map != m_Settings)
1659 {
1660 m_Settings.clear();
1661 setAllSettings(map);
1662 }
1663 }
1664 else
1665 m_Settings = m_GlobalSettings;
1666 }
1667
1668 opticalTrainCombo->blockSignals(false);
1669}
1670
1671///////////////////////////////////////////////////////////////////////////////////////
1672///
1673///////////////////////////////////////////////////////////////////////////////////////
1674void DarkLibrary::loadGlobalSettings()
1675{
1676 QString key;
1677 QVariant value;
1678
1679 QVariantMap settings;
1680 // All Combo Boxes
1681 for (auto &oneWidget : findChildren<QComboBox*>())
1682 {
1683 if (oneWidget->objectName() == "opticalTrainCombo")
1684 continue;
1685
1686 key = oneWidget->objectName();
1687 value = Options::self()->property(key.toLatin1());
1688 if (value.isValid() && oneWidget->count() > 0)
1689 {
1690 oneWidget->setCurrentText(value.toString());
1691 settings[key] = value;
1692 }
1693 }
1694
1695 // All Double Spin Boxes
1696 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1697 {
1698 key = oneWidget->objectName();
1699 value = Options::self()->property(key.toLatin1());
1700 if (value.isValid())
1701 {
1702 oneWidget->setValue(value.toDouble());
1703 settings[key] = value;
1704 }
1705 }
1706
1707 // All Spin Boxes
1708 for (auto &oneWidget : findChildren<QSpinBox*>())
1709 {
1710 key = oneWidget->objectName();
1711 value = Options::self()->property(key.toLatin1());
1712 if (value.isValid())
1713 {
1714 oneWidget->setValue(value.toInt());
1715 settings[key] = value;
1716 }
1717 }
1718
1719 // All Checkboxes
1720 for (auto &oneWidget : findChildren<QCheckBox*>())
1721 {
1722 key = oneWidget->objectName();
1723 value = Options::self()->property(key.toLatin1());
1724 if (value.isValid())
1725 {
1726 oneWidget->setChecked(value.toBool());
1727 settings[key] = value;
1728 }
1729 }
1730
1731 m_GlobalSettings = m_Settings = settings;
1732}
1733
1734
1735///////////////////////////////////////////////////////////////////////////////////////
1736///
1737///////////////////////////////////////////////////////////////////////////////////////
1738void DarkLibrary::connectSettings()
1739{
1740 // All Combo Boxes
1741 for (auto &oneWidget : findChildren<QComboBox*>())
1742 connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::DarkLibrary::syncSettings);
1743
1744 // All Double Spin Boxes
1745 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1746 connect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings);
1747
1748 // All Spin Boxes
1749 for (auto &oneWidget : findChildren<QSpinBox*>())
1750 connect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings);
1751
1752 // All Checkboxes
1753 for (auto &oneWidget : findChildren<QCheckBox*>())
1754 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings);
1755
1756 // All Radio buttons
1757 for (auto &oneWidget : findChildren<QRadioButton*>())
1758 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings);
1759
1760 // Train combo box should NOT be synced.
1761 disconnect(opticalTrainCombo, QOverload<int>::of(&QComboBox::activated), this, &Ekos::DarkLibrary::syncSettings);
1762}
1763
1764///////////////////////////////////////////////////////////////////////////////////////
1765///
1766///////////////////////////////////////////////////////////////////////////////////////
1767void DarkLibrary::disconnectSettings()
1768{
1769 // All Combo Boxes
1770 for (auto &oneWidget : findChildren<QComboBox*>())
1771 disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::DarkLibrary::syncSettings);
1772
1773 // All Double Spin Boxes
1774 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1775 disconnect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings);
1776
1777 // All Spin Boxes
1778 for (auto &oneWidget : findChildren<QSpinBox*>())
1779 disconnect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings);
1780
1781 // All Checkboxes
1782 for (auto &oneWidget : findChildren<QCheckBox*>())
1783 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings);
1784
1785 // All Radio buttons
1786 for (auto &oneWidget : findChildren<QRadioButton*>())
1787 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings);
1788
1789}
1790
1791///////////////////////////////////////////////////////////////////////////////////////////
1792///
1793///////////////////////////////////////////////////////////////////////////////////////////
1794QVariantMap DarkLibrary::getAllSettings() const
1795{
1796 QVariantMap settings;
1797
1798 // All Combo Boxes
1799 for (auto &oneWidget : findChildren<QComboBox*>())
1800 settings.insert(oneWidget->objectName(), oneWidget->currentText());
1801
1802 // All Double Spin Boxes
1803 for (auto &oneWidget : findChildren<QDoubleSpinBox*>())
1804 settings.insert(oneWidget->objectName(), oneWidget->value());
1805
1806 // All Spin Boxes
1807 for (auto &oneWidget : findChildren<QSpinBox*>())
1808 settings.insert(oneWidget->objectName(), oneWidget->value());
1809
1810 // All Checkboxes
1811 for (auto &oneWidget : findChildren<QCheckBox*>())
1812 settings.insert(oneWidget->objectName(), oneWidget->isChecked());
1813
1814 return settings;
1815}
1816
1817///////////////////////////////////////////////////////////////////////////////////////////
1818///
1819///////////////////////////////////////////////////////////////////////////////////////////
1820void DarkLibrary::setAllSettings(const QVariantMap &settings)
1821{
1822 // Disconnect settings that we don't end up calling syncSettings while
1823 // performing the changes.
1824 disconnectSettings();
1825
1826 for (auto &name : settings.keys())
1827 {
1828 // Combo
1829 auto comboBox = findChild<QComboBox*>(name);
1830 if (comboBox)
1831 {
1832 syncControl(settings, name, comboBox);
1833 continue;
1834 }
1835
1836 // Double spinbox
1837 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name);
1838 if (doubleSpinBox)
1839 {
1840 syncControl(settings, name, doubleSpinBox);
1841 continue;
1842 }
1843
1844 // spinbox
1845 auto spinBox = findChild<QSpinBox*>(name);
1846 if (spinBox)
1847 {
1848 syncControl(settings, name, spinBox);
1849 continue;
1850 }
1851
1852 // checkbox
1853 auto checkbox = findChild<QCheckBox*>(name);
1854 if (checkbox)
1855 {
1856 syncControl(settings, name, checkbox);
1857 continue;
1858 }
1859
1860 // Radio button
1861 auto radioButton = findChild<QRadioButton*>(name);
1862 if (radioButton)
1863 {
1864 syncControl(settings, name, radioButton);
1865 continue;
1866 }
1867 }
1868
1869 // Sync to options
1870 for (auto &key : settings.keys())
1871 {
1872 auto value = settings[key];
1873 // Save immediately
1874 Options::self()->setProperty(key.toLatin1(), value);
1875
1876 m_Settings[key] = value;
1877 m_GlobalSettings[key] = value;
1878 }
1879
1880 emit settingsUpdated(getAllSettings());
1881
1882 // Save to optical train specific settings as well
1883 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
1884 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::DarkLibrary, m_Settings);
1885
1886 // Restablish connections
1887 connectSettings();
1888}
1889
1890///////////////////////////////////////////////////////////////////////////////////////////
1891///
1892///////////////////////////////////////////////////////////////////////////////////////////
1893bool DarkLibrary::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget)
1894{
1895 QSpinBox *pSB = nullptr;
1896 QDoubleSpinBox *pDSB = nullptr;
1897 QCheckBox *pCB = nullptr;
1898 QComboBox *pComboBox = nullptr;
1899 QRadioButton *pRadioButton = nullptr;
1900 bool ok = false;
1901
1902 if ((pSB = qobject_cast<QSpinBox *>(widget)))
1903 {
1904 const int value = settings[key].toInt(&ok);
1905 if (ok)
1906 {
1907 pSB->setValue(value);
1908 return true;
1909 }
1910 }
1911 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget)))
1912 {
1913 const double value = settings[key].toDouble(&ok);
1914 if (ok)
1915 {
1916 pDSB->setValue(value);
1917 return true;
1918 }
1919 }
1920 else if ((pCB = qobject_cast<QCheckBox *>(widget)))
1921 {
1922 const bool value = settings[key].toBool();
1923 if (value != pCB->isChecked())
1924 pCB->click();
1925 return true;
1926 }
1927 // ONLY FOR STRINGS, not INDEX
1928 else if ((pComboBox = qobject_cast<QComboBox *>(widget)))
1929 {
1930 const QString value = settings[key].toString();
1931 pComboBox->setCurrentText(value);
1932 return true;
1933 }
1934 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget)))
1935 {
1936 const bool value = settings[key].toBool();
1937 if (value)
1938 pRadioButton->click();
1939 return true;
1940 }
1941
1942 return false;
1943};
1944
1945///////////////////////////////////////////////////////////////////////////////////////
1946///
1947///////////////////////////////////////////////////////////////////////////////////////
1948void DarkLibrary::syncSettings()
1949{
1950 QDoubleSpinBox *dsb = nullptr;
1951 QSpinBox *sb = nullptr;
1952 QCheckBox *cb = nullptr;
1953 QComboBox *cbox = nullptr;
1954 QRadioButton *cradio = nullptr;
1955
1956 QString key;
1957 QVariant value;
1958
1959 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender())))
1960 {
1961 key = dsb->objectName();
1962 value = dsb->value();
1963
1964 }
1965 else if ( (sb = qobject_cast<QSpinBox*>(sender())))
1966 {
1967 key = sb->objectName();
1968 value = sb->value();
1969 }
1970 else if ( (cb = qobject_cast<QCheckBox*>(sender())))
1971 {
1972 key = cb->objectName();
1973 value = cb->isChecked();
1974 }
1975 else if ( (cbox = qobject_cast<QComboBox*>(sender())))
1976 {
1977 key = cbox->objectName();
1978 value = cbox->currentText();
1979 }
1980 else if ( (cradio = qobject_cast<QRadioButton*>(sender())))
1981 {
1982 key = cradio->objectName();
1983 // Discard false requests
1984 if (cradio->isChecked() == false)
1985 {
1986 m_Settings.remove(key);
1987 return;
1988 }
1989 value = true;
1990 }
1991
1992 // Save immediately
1993 Options::self()->setProperty(key.toLatin1(), value);
1994 m_Settings[key] = value;
1995 m_GlobalSettings[key] = value;
1996
1997 m_DebounceTimer.start();
1998}
1999
2000///////////////////////////////////////////////////////////////////////////////////////////
2001///
2002///////////////////////////////////////////////////////////////////////////////////////////
2003void DarkLibrary::settleSettings()
2004{
2005 emit settingsUpdated(getAllSettings());
2006 Options::self()->save();
2007 // Save to optical train specific settings as well
2008 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText()));
2009 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::DarkLibrary, m_Settings);
2010}
2011
2012///////////////////////////////////////////////////////////////////////////////////////
2013///
2014///////////////////////////////////////////////////////////////////////////////////////
2015QJsonObject DarkLibrary::getDefectSettings()
2016{
2017 QStringList darkMasters;
2018 for (int i = 0; i < masterDarksCombo->count(); i++)
2019 darkMasters << masterDarksCombo->itemText(i);
2020
2021 QJsonObject createDefectMaps =
2022 {
2023 {"masterTime", masterTime->text()},
2024 {"masterDarks", darkMasters.join('|')},
2025 {"masterExposure", masterExposure->text()},
2026 {"masterTempreture", masterTemperature->text()},
2027 {"masterMean", masterMean->text()},
2028 {"masterMedian", masterMedian->text()},
2029 {"masterDeviation", masterDeviation->text()},
2030 {"hotPixelsEnabled", hotPixelsEnabled->isChecked()},
2031 {"coldPixelsEnabled", coldPixelsEnabled->isChecked()},
2032 };
2033 return createDefectMaps;
2034}
2035
2036
2037
2038}
2039
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 Mon Nov 18 2024 12:16:40 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.