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 "auxiliary/ksmessagebox.h"
9 #include "auxiliary/ksnotification.h"
10
11 #include "Options.h"
12
13 #include "ekos/manager.h"
14 #include "ekos/capture/capture.h"
15 #include "ekos/capture/sequencejob.h"
16 #include "kstars.h"
17 #include "kspaths.h"
18 #include "kstarsdata.h"
19 #include "fitsviewer/fitsdata.h"
20 #include "fitsviewer/fitsview.h"
21 #include "fitsviewer/fitshistogramview.h"
22
23 #include "ekos_debug.h"
24
25 #include <QDesktopServices>
26 #include <QSqlRecord>
27 #include <QSqlTableModel>
28 #include <QStatusBar>
29 #include <QtConcurrent>
30 #include <algorithm>
31 #include <array>
32
33 namespace Ekos
34 {
35 DarkLibrary *DarkLibrary::_DarkLibrary = nullptr;
36
Instance()37 DarkLibrary *DarkLibrary::Instance()
38 {
39 if (_DarkLibrary == nullptr)
40 _DarkLibrary = new DarkLibrary(Manager::Instance());
41
42 return _DarkLibrary;
43 }
44
DarkLibrary(QWidget * parent)45 DarkLibrary::DarkLibrary(QWidget *parent) : QDialog(parent)
46 {
47 setupUi(this);
48
49 m_StatusBar = new QStatusBar(this);
50 m_StatusLabel = new QLabel(i18n("Idle"), this);
51 m_FileLabel = new QLabel(this);
52 m_FileLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
53
54 m_StatusBar->insertPermanentWidget(0, m_StatusLabel);
55 m_StatusBar->insertPermanentWidget(1, m_FileLabel, 1);
56 mainLayout->addWidget(m_StatusBar);
57
58 histogramView->setProperty("axesLabelEnabled", false);
59 //histogramView->setProperty("linear", true);
60
61 QDir writableDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation));
62 writableDir.mkpath("darks");
63 writableDir.mkpath("defectmaps");
64
65 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
66 // Dark Generation Connections
67 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
68 m_CurrentDarkFrame.reset(new FITSData(), &QObject::deleteLater);
69
70 m_DarkCameras = Options::darkCameras();
71 m_DefectCameras = Options::defectCameras();
72
73 connect(darkHandlingButtonGroup, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::buttonToggled), [this]()
74 {
75 const QString device = m_CurrentCamera->getDeviceName();
76 if (preferDarksRadio->isChecked())
77 {
78 m_DefectCameras.removeOne(device);
79 if (!m_DarkCameras.contains(device))
80 m_DarkCameras.append(device);
81 }
82 else
83 {
84 m_DarkCameras.removeOne(device);
85 if (!m_DefectCameras.contains(device))
86 m_DefectCameras.append(device);
87 }
88
89 Options::setDarkCameras(m_DarkCameras);
90 Options::setDefectCameras(m_DefectCameras);
91 });
92
93 connect(darkTableView, &QAbstractItemView::doubleClicked, this, &DarkLibrary::loadDarkFITS);
94 connect(openDarksFolderB, &QPushButton::clicked, this, &DarkLibrary::openDarksFolder);
95 connect(clearAllB, &QPushButton::clicked, this, &DarkLibrary::clearAll);
96 connect(clearRowB, &QPushButton::clicked, this, &DarkLibrary::clearRow);
97 connect(clearExpiredB, &QPushButton::clicked, this, &DarkLibrary::clearExpired);
98 connect(refreshB, &QPushButton::clicked, this, &DarkLibrary::reloadDarksFromDatabase);
99
100 connect(cameraS, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), [this]()
101 {
102 checkCamera();
103 reloadDarksFromDatabase();
104 });
105
106 connect(&m_DarkFrameFutureWatcher, &QFutureWatcher<bool>::finished, [this]()
107 {
108 // If loading is successful, then set it in current dark view
109 if (m_DarkFrameFutureWatcher.result())
110 {
111 m_DarkView->loadData(m_CurrentDarkFrame);
112 loadCurrentMasterDefectMap();
113 histogramView->setImageData(m_CurrentDarkFrame);
114 if (!Options::nonLinearHistogram() && !m_CurrentDarkFrame->isHistogramConstructed())
115 m_CurrentDarkFrame->constructHistogram();
116 populateMasterMetedata();
117 }
118 else
119 m_FileLabel->setText(i18n("Failed to load %1: %2", m_MasterDarkFrameFilename, m_CurrentDarkFrame->getLastError()));
120
121 });
122 connect(m_CurrentDarkFrame.data(), &FITSData::histogramReady, [this]()
123 {
124 histogramView->setEnabled(true);
125 histogramView->reset();
126 histogramView->syncGUI();
127 });
128
129 connect(masterDarksCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this](int index)
130 {
131 DarkLibrary::loadCurrentMasterDark(cameraS->currentText(), index);
132 });
133
134
135 connect(minExposureSpin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime);
136 connect(maxExposureSpin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime);
137 connect(exposureStepSin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime);
138
139 connect(minTemperatureSpin, &QDoubleSpinBox::editingFinished, [this]()
140 {
141 maxTemperatureSpin->setMinimum(minTemperatureSpin->value());
142 countDarkTotalTime();
143 });
144 connect(maxTemperatureSpin, &QDoubleSpinBox::editingFinished, [this]()
145 {
146 minTemperatureSpin->setMaximum(maxTemperatureSpin->value());
147 countDarkTotalTime();
148 });
149 connect(temperatureStepSpin, &QDoubleSpinBox::editingFinished, [this]()
150 {
151 maxTemperatureSpin->setMinimum(minTemperatureSpin->value());
152 minTemperatureSpin->setMaximum(maxTemperatureSpin->value());
153 countDarkTotalTime();
154 });
155
156 connect(countSpin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime);
157
158 connect(binningButtonGroup, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::buttonToggled), this, [this](int,
159 bool)
160 {
161 countDarkTotalTime();
162 });
163
164 connect(startB, &QPushButton::clicked, [this]()
165 {
166 generateDarkJobs();
167 executeDarkJobs();
168 });
169
170 connect(stopB, &QPushButton::clicked, this, &DarkLibrary::stopDarkJobs);
171
172 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
173 // Master Darks Database Connections
174 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
175 kcfg_DarkLibraryDuration->setValue(Options::darkLibraryDuration());
176 connect(kcfg_DarkLibraryDuration, &QDoubleSpinBox::editingFinished, [this]()
177 {
178 Options::setDarkLibraryDuration(kcfg_DarkLibraryDuration->value());
179 });
180
181 kcfg_MaxDarkTemperatureDiff->setValue(Options::maxDarkTemperatureDiff());
182 connect(kcfg_MaxDarkTemperatureDiff, &QDoubleSpinBox::editingFinished, [this]()
183 {
184 Options::setMaxDarkTemperatureDiff(kcfg_MaxDarkTemperatureDiff->value());
185 });
186
187 KStarsData::Instance()->userdb()->GetAllDarkFrames(m_DarkFramesDatabaseList);
188 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
189 // Defect Map Connections
190 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
191 connect(darkTabsWidget, &QTabWidget::currentChanged, [this](int index)
192 {
193 m_DarkView->setDefectMapEnabled(index == 1 && m_CurrentDefectMap);
194 });
195 connect(aggresivenessHotSlider, &QSlider::valueChanged, aggresivenessHotSpin, &QSpinBox::setValue);
196 connect(aggresivenessColdSlider, &QSlider::valueChanged, aggresivenessColdSpin, &QSpinBox::setValue);
197 connect(hotPixelsEnabled, &QCheckBox::toggled, [this](bool toggled)
198 {
199 if (m_CurrentDefectMap)
200 m_CurrentDefectMap->setProperty("HotEnabled", toggled);
201 });
202 connect(coldPixelsEnabled, &QCheckBox::toggled, [this](bool toggled)
203 {
204 if (m_CurrentDefectMap)
205 m_CurrentDefectMap->setProperty("ColdEnabled", toggled);
206 });
207 connect(generateMapB, &QPushButton::clicked, [this]()
208 {
209 if (m_CurrentDefectMap)
210 {
211 m_CurrentDefectMap->setProperty("HotPixelAggressiveness", aggresivenessHotSpin->value());
212 m_CurrentDefectMap->setProperty("ColdPixelAggressiveness", aggresivenessColdSpin->value());
213 m_CurrentDefectMap->filterPixels();
214 }
215 });
216 connect(resetMapParametersB, &QPushButton::clicked, [this]()
217 {
218 if (m_CurrentDefectMap)
219 {
220 aggresivenessHotSlider->setValue(75);
221 aggresivenessColdSlider->setValue(75);
222 m_CurrentDefectMap->setProperty("HotPixelAggressiveness", 75);
223 m_CurrentDefectMap->setProperty("ColdPixelAggressiveness", 75);
224 m_CurrentDefectMap->filterPixels();
225 }
226 });
227 connect(saveMapB, &QPushButton::clicked, this, &DarkLibrary::saveDefectMap);
228 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
229 // Settings & Initialization
230 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
231 m_RememberFITSViewer = Options::useFITSViewer();
232 m_RememberSummaryView = Options::useSummaryPreview();
233 initView();
234 }
235
~DarkLibrary()236 DarkLibrary::~DarkLibrary()
237 {
238 }
239
240 ///////////////////////////////////////////////////////////////////////////////////////
241 ///
242 ///////////////////////////////////////////////////////////////////////////////////////
refreshFromDB()243 void DarkLibrary::refreshFromDB()
244 {
245 KStarsData::Instance()->userdb()->GetAllDarkFrames(m_DarkFramesDatabaseList);
246 }
247
248 ///////////////////////////////////////////////////////////////////////////////////////
249 ///
250 ///////////////////////////////////////////////////////////////////////////////////////
findDarkFrame(ISD::CCDChip * m_TargetChip,double duration,QSharedPointer<FITSData> & darkData)251 bool DarkLibrary::findDarkFrame(ISD::CCDChip *m_TargetChip, double duration, QSharedPointer<FITSData> &darkData)
252 {
253 QVariantMap bestCandidate;
254 for (auto &map : m_DarkFramesDatabaseList)
255 {
256 // First check CCD name matches and check if we are on the correct chip
257 if (map["ccd"].toString() == m_TargetChip->getCCD()->getDeviceName() &&
258 map["chip"].toInt() == static_cast<int>(m_TargetChip->getType()))
259 {
260 int binX, binY;
261 m_TargetChip->getBinning(&binX, &binY);
262
263 // Then check if binning is the same
264 if (map["binX"].toInt() == binX && map["binY"].toInt() == binY)
265 {
266 // If camera has an active cooler, then we check temperature against the absolute threshold.
267 if (m_TargetChip->getCCD()->hasCoolerControl())
268 {
269 double temperature = 0;
270 m_TargetChip->getCCD()->getTemperature(&temperature);
271 double darkTemperature = map["temperature"].toDouble();
272 // If different is above threshold, it is completely rejected.
273 if (darkTemperature != INVALID_VALUE && fabs(darkTemperature - temperature) > Options::maxDarkTemperatureDiff())
274 continue;
275 }
276
277 if (bestCandidate.isEmpty())
278 {
279 bestCandidate = map;
280 continue;
281 }
282
283 // We try to find the best frame
284 // Frame closest in exposure duration wins
285 // Frame with temperature closest to stored temperature wins (if temperature is reported)
286 uint32_t thisMapScore = 0;
287 uint32_t bestCandidateScore = 0;
288
289 // Else we check for the closest passive temperature
290 if (m_TargetChip->getCCD()->hasCooler())
291 {
292 double temperature = 0;
293 m_TargetChip->getCCD()->getTemperature(&temperature);
294 double diffMap = std::fabs(temperature - map["temperature"].toDouble());
295 double diffBest = std::fabs(temperature - bestCandidate["temperature"].toDouble());
296 // Prefer temperatures closest to target
297 if (diffMap < diffBest)
298 thisMapScore++;
299 else if (diffBest < diffMap)
300 bestCandidateScore++;
301 }
302
303 // Duration has a higher score priority over temperature
304 {
305 double diffMap = std::fabs(map["duration"].toDouble() - duration);
306 double diffBest = std::fabs(bestCandidate["duration"].toDouble() - duration);
307 if (diffMap < diffBest)
308 thisMapScore += 2;
309 else if (diffBest < diffMap)
310 bestCandidateScore += 2;
311 }
312
313 // More recent has a higher score than older.
314 {
315 const QDateTime now = QDateTime::currentDateTime();
316 int64_t diffMap = map["timestamp"].toDateTime().secsTo(now);
317 int64_t diffBest = bestCandidate["timestamp"].toDateTime().secsTo(now);
318 if (diffMap < diffBest)
319 thisMapScore += 2;
320 else if (diffBest < diffMap)
321 bestCandidateScore += 2;
322 }
323
324 // Find candidate with closest time in case we have multiple defect maps
325 if (thisMapScore > bestCandidateScore)
326 bestCandidate = map;
327 }
328 }
329 }
330
331 if (bestCandidate.isEmpty())
332 return false;
333
334 if (fabs(bestCandidate["duration"].toDouble() - duration) > 3)
335 emit i18n("Using available dark frame with %1 seconds exposure. Please take a dark frame with %1 seconds exposure for more accurate results.",
336 QString::number(bestCandidate["duration"].toDouble(), 'f', 1),
337 QString::number(duration, 'f', 1));
338
339 QString filename = bestCandidate["filename"].toString();
340
341 // Finally check if the duration is acceptable
342 QDateTime frameTime = bestCandidate["timestamp"].toDateTime();
343 if (frameTime.daysTo(QDateTime::currentDateTime()) > Options::darkLibraryDuration())
344 {
345 emit i18n("Dark frame %s is expired. Please create new master dark.", filename);
346 return false;
347 }
348
349 if (m_CachedDarkFrames.contains(filename))
350 {
351 darkData = m_CachedDarkFrames[filename];
352 return true;
353 }
354
355 // Finally we made it, let's put it in the hash
356 if (cacheDarkFrameFromFile(filename))
357 {
358 darkData = m_CachedDarkFrames[filename];
359 return true;
360 }
361
362 // Remove bad dark frame
363 emit newLog(i18n("Removing bad dark frame file %1", filename));
364 m_CachedDarkFrames.remove(filename);
365 QFile::remove(filename);
366 KStarsData::Instance()->userdb()->DeleteDarkFrame(filename);
367 return false;
368
369 }
370
371 ///////////////////////////////////////////////////////////////////////////////////////
372 ///
373 ///////////////////////////////////////////////////////////////////////////////////////
findDefectMap(ISD::CCDChip * m_TargetChip,double duration,QSharedPointer<DefectMap> & defectMap)374 bool DarkLibrary::findDefectMap(ISD::CCDChip *m_TargetChip, double duration, QSharedPointer<DefectMap> &defectMap)
375 {
376 QVariantMap bestCandidate;
377 for (auto &map : m_DarkFramesDatabaseList)
378 {
379 if (map["defectmap"].toString().isEmpty())
380 continue;
381
382 // First check CCD name matches and check if we are on the correct chip
383 if (map["ccd"].toString() == m_TargetChip->getCCD()->getDeviceName() &&
384 map["chip"].toInt() == static_cast<int>(m_TargetChip->getType()))
385 {
386 int binX, binY;
387 m_TargetChip->getBinning(&binX, &binY);
388
389 // Then check if binning is the same
390 if (map["binX"].toInt() == binX && map["binY"].toInt() == binY)
391 {
392 if (bestCandidate.isEmpty())
393 {
394 bestCandidate = map;
395 continue;
396 }
397
398 // We try to find the best frame
399 // Frame closest in exposure duration wins
400 // Frame with temperature closest to stored temperature wins (if temperature is reported)
401 uint32_t thisMapScore = 0;
402 uint32_t bestCandidateScore = 0;
403
404 // Else we check for the closest passive temperature
405 if (m_TargetChip->getCCD()->hasCooler())
406 {
407 double temperature = 0;
408 m_TargetChip->getCCD()->getTemperature(&temperature);
409 double diffMap = std::fabs(temperature - map["temperature"].toDouble());
410 double diffBest = std::fabs(temperature - bestCandidate["temperature"].toDouble());
411 // Prefer temperatures closest to target
412 if (diffMap < diffBest)
413 thisMapScore++;
414 else if (diffBest < diffMap)
415 bestCandidateScore++;
416 }
417
418 // Duration has a higher score priority over temperature
419 double diffMap = std::fabs(map["duration"].toDouble() - duration);
420 double diffBest = std::fabs(bestCandidate["duration"].toDouble() - duration);
421 if (diffMap < diffBest)
422 thisMapScore += 2;
423 else if (diffBest < diffMap)
424 bestCandidateScore += 2;
425
426 // Find candidate with closest time in case we have multiple defect maps
427 if (thisMapScore > bestCandidateScore)
428 bestCandidate = map;
429 }
430 }
431 }
432
433
434 if (bestCandidate.isEmpty())
435 return false;
436
437 QString darkFilename = bestCandidate["filename"].toString();
438 QString defectFilename = bestCandidate["defectmap"].toString();
439
440 if (darkFilename.isEmpty() || defectFilename.isEmpty())
441 return false;
442
443 if (m_CachedDefectMaps.contains(darkFilename))
444 {
445 defectMap = m_CachedDefectMaps[darkFilename];
446 return true;
447 }
448
449 // Finally we made it, let's put it in the hash
450 if (cacheDefectMapFromFile(darkFilename, defectFilename))
451 {
452 defectMap = m_CachedDefectMaps[darkFilename];
453 return true;
454 }
455 else
456 {
457 // Remove bad dark frame
458 emit newLog(i18n("Failed to load defect map %1", defectFilename));
459 return false;
460 }
461 }
462
463 ///////////////////////////////////////////////////////////////////////////////////////
464 ///
465 ///////////////////////////////////////////////////////////////////////////////////////
cacheDefectMapFromFile(const QString & key,const QString & filename)466 bool DarkLibrary::cacheDefectMapFromFile(const QString &key, const QString &filename)
467 {
468 QSharedPointer<DefectMap> oneMap;
469 oneMap.reset(new DefectMap());
470
471 if (oneMap->load(filename))
472 {
473 oneMap->filterPixels();
474 m_CachedDefectMaps[key] = oneMap;
475 return true;
476 }
477
478 emit newLog(i18n("Failed to load defect map file %1", filename));
479 return false;
480 }
481
482 ///////////////////////////////////////////////////////////////////////////////////////
483 ///
484 ///////////////////////////////////////////////////////////////////////////////////////
cacheDarkFrameFromFile(const QString & filename)485 bool DarkLibrary::cacheDarkFrameFromFile(const QString &filename)
486 {
487 QFuture<bool> rc = m_CurrentDarkFrame->loadFromFile(filename);
488
489 rc.waitForFinished();
490 if (rc.result())
491 m_CachedDarkFrames[filename] = m_CurrentDarkFrame;
492 else
493 {
494 emit newLog(i18n("Failed to load dark frame file %1", filename));
495 }
496
497 return rc;
498 }
499
500 ///////////////////////////////////////////////////////////////////////////////////////
501 ///
502 ///////////////////////////////////////////////////////////////////////////////////////
processNewImage(SequenceJob * job,const QSharedPointer<FITSData> & data)503 void DarkLibrary::processNewImage(SequenceJob *job, const QSharedPointer<FITSData> &data)
504 {
505 Q_UNUSED(data)
506 if (job->getStatus() == SequenceJob::JOB_IDLE)
507 return;
508
509 if (job->getCompleted() == job->getCount())
510 {
511 QJsonObject metadata
512 {
513 {"camera", m_CurrentCamera->getDeviceName()},
514 {"chip", m_TargetChip->getType()},
515 {"binx", job->getXBin()},
516 {"biny", job->getYBin()},
517 {"duration", job->getExposure()}
518 };
519
520 // Record temperature
521 if (m_CurrentCamera->hasCooler())
522 metadata["temperature"] = job->getCurrentTemperature();
523
524 metadata["count"] = job->getCount();
525 generateMasterFrame(m_CurrentDarkFrame, metadata);
526 reloadDarksFromDatabase();
527 populateMasterMetedata();
528 histogramView->setImageData(m_CurrentDarkFrame);
529 if (!Options::nonLinearHistogram() && !m_CurrentDarkFrame->isHistogramConstructed())
530 m_CurrentDarkFrame->constructHistogram();
531 }
532 }
533
534 ///////////////////////////////////////////////////////////////////////////////////////
535 ///
536 ///////////////////////////////////////////////////////////////////////////////////////
processNewBLOB(IBLOB * bp)537 void DarkLibrary::processNewBLOB(IBLOB *bp)
538 {
539 QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<char *>(bp->blob), bp->size);
540 if (!m_CurrentDarkFrame->loadFromBuffer(buffer, "fits"))
541 {
542 m_FileLabel->setText(i18n("Failed to process dark data."));
543 return;
544 }
545
546 if (!m_DarkView->loadData(m_CurrentDarkFrame))
547 {
548 m_FileLabel->setText(i18n("Failed to load dark data."));
549 return;
550 }
551
552 uint32_t totalElements = m_CurrentDarkFrame->channels() * m_CurrentDarkFrame->samplesPerChannel();
553 if (totalElements != m_DarkMasterBuffer.size())
554 m_DarkMasterBuffer.assign(totalElements, 0);
555
556 aggregate(m_CurrentDarkFrame);
557 darkProgress->setValue(darkProgress->value() + 1);
558 m_StatusLabel->setText(i18n("Received %1/%2 images.", darkProgress->value(), darkProgress->maximum()));
559 }
560
561 ///////////////////////////////////////////////////////////////////////////////////////
562 ///
563 ///////////////////////////////////////////////////////////////////////////////////////
Release()564 void DarkLibrary::Release()
565 {
566 delete (_DarkLibrary);
567 _DarkLibrary = nullptr;
568
569 // m_Cameras.clear();
570 // cameraS->clear();
571 // m_CurrentCamera = nullptr;
572 }
573
574 ///////////////////////////////////////////////////////////////////////////////////////
575 ///
576 ///////////////////////////////////////////////////////////////////////////////////////
closeEvent(QCloseEvent * ev)577 void DarkLibrary::closeEvent(QCloseEvent *ev)
578 {
579 Q_UNUSED(ev)
580 Options::setUseFITSViewer(m_RememberFITSViewer);
581 Options::setUseFITSViewer(m_RememberSummaryView);
582 if (!m_RememberFITSDirectory.isEmpty())
583 m_CaptureModule->fileDirectoryT->setText(m_RememberFITSDirectory);
584 if (m_JobsGenerated)
585 {
586 m_JobsGenerated = false;
587 m_CaptureModule->clearSequenceQueue();
588 m_CaptureModule->setPresetSettings(m_PresetSettings);
589 }
590 }
591
592 ///////////////////////////////////////////////////////////////////////////////////////
593 ///
594 ///////////////////////////////////////////////////////////////////////////////////////
setCompleted()595 void DarkLibrary::setCompleted()
596 {
597 startB->setEnabled(true);
598 stopB->setEnabled(false);
599
600 Options::setUseFITSViewer(m_RememberFITSViewer);
601 Options::setUseFITSViewer(m_RememberSummaryView);
602 if (!m_RememberFITSDirectory.isEmpty())
603 m_CaptureModule->fileDirectoryT->setText(m_RememberFITSDirectory);
604 if (m_JobsGenerated)
605 {
606 m_JobsGenerated = false;
607 m_CaptureModule->clearSequenceQueue();
608 m_CaptureModule->setPresetSettings(m_PresetSettings);
609 }
610
611 m_CurrentCamera->disconnect(this);
612 m_CaptureModule->disconnect(this);
613 }
614
615 ///////////////////////////////////////////////////////////////////////////////////////
616 ///
617 ///////////////////////////////////////////////////////////////////////////////////////
clearExpired()618 void DarkLibrary::clearExpired()
619 {
620 if (darkFramesModel->rowCount() == 0)
621 return;
622
623 // Anything before this must go
624 QDateTime expiredDate = QDateTime::currentDateTime().addDays(kcfg_DarkLibraryDuration->value() * -1);
625
626 QSqlDatabase userdb = QSqlDatabase::database("userdb");
627 userdb.open();
628 QSqlTableModel darkframe(nullptr, userdb);
629 darkframe.setEditStrategy(QSqlTableModel::OnManualSubmit);
630 darkframe.setTable("darkframe");
631 // Select all those that already expired.
632 darkframe.setFilter("ccd LIKE \'" + m_CurrentCamera->getDeviceName() + "\' AND timestamp < \'" + expiredDate.toString(
633 Qt::ISODate) + "\'");
634
635 darkframe.select();
636
637 // Now remove all the expired files from disk
638 for (int i = 0; i < darkframe.rowCount(); ++i)
639 {
640 QString oneFile = darkframe.record(i).value("filename").toString();
641 QFile::remove(oneFile);
642 QString defectMap = darkframe.record(i).value("defectmap").toString();
643 if (defectMap.isEmpty() == false)
644 QFile::remove(defectMap);
645
646 }
647
648 // And remove them from the database
649 darkframe.removeRows(0, darkframe.rowCount());
650 darkframe.submitAll();
651 userdb.close();
652
653 Ekos::DarkLibrary::Instance()->refreshFromDB();
654
655 reloadDarksFromDatabase();
656 }
657
658 ///////////////////////////////////////////////////////////////////////////////////////
659 ///
660 ///////////////////////////////////////////////////////////////////////////////////////
clearBuffers()661 void DarkLibrary::clearBuffers()
662 {
663 m_CurrentDarkFrame.clear();
664 // Should clear existing view
665 m_CurrentDarkFrame.reset(new FITSData(), &QObject::deleteLater);
666 connect(m_CurrentDarkFrame.data(), &FITSData::histogramReady, [this]()
667 {
668 histogramView->setEnabled(true);
669 histogramView->reset();
670 histogramView->syncGUI();
671 });
672 m_DarkView->clearData();
673 m_CurrentDefectMap.clear();
674
675 }
676 ///////////////////////////////////////////////////////////////////////////////////////
677 ///
678 ///////////////////////////////////////////////////////////////////////////////////////
clearAll()679 void DarkLibrary::clearAll()
680 {
681 if (darkFramesModel->rowCount() == 0)
682 return;
683
684 if (KMessageBox::questionYesNo(KStars::Instance(),
685 i18n("Are you sure you want to delete all dark frames images and data?")) ==
686 KMessageBox::No)
687 return;
688
689 QSqlDatabase userdb = QSqlDatabase::database("userdb");
690 userdb.open();
691 QSqlTableModel darkframe(nullptr, userdb);
692 darkFramesModel->setEditStrategy(QSqlTableModel::OnManualSubmit);
693 darkframe.setTable("darkframe");
694 darkframe.setFilter("ccd LIKE \'" + m_CurrentCamera->getDeviceName() + "\'");
695 darkFramesModel->select();
696
697 // Now remove all the expired files from disk
698 for (int i = 0; i < darkframe.rowCount(); ++i)
699 {
700 QString oneFile = darkframe.record(i).value("filename").toString();
701 QFile::remove(oneFile);
702 QString defectMap = darkframe.record(i).value("defectmap").toString();
703 if (defectMap.isEmpty() == false)
704 QFile::remove(defectMap);
705
706 }
707
708 darkFramesModel->removeRows(0, darkFramesModel->rowCount());
709 darkFramesModel->submitAll();
710 userdb.close();
711
712 Ekos::DarkLibrary::Instance()->refreshFromDB();
713
714 // Refesh db entries for other cameras
715 reloadDarksFromDatabase();
716 }
717
718 ///////////////////////////////////////////////////////////////////////////////////////
719 ///
720 ///////////////////////////////////////////////////////////////////////////////////////
clearRow()721 void DarkLibrary::clearRow()
722 {
723 QSqlDatabase userdb = QSqlDatabase::database("userdb");
724 int row = darkTableView->currentIndex().row();
725
726 QSqlRecord record = darkFramesModel->record(row);
727 QString filename = record.value("filename").toString();
728 QString defectMap = record.value("defectmap").toString();
729 QFile::remove(filename);
730 if (!defectMap.isEmpty())
731 QFile::remove(defectMap);
732
733 userdb.open();
734
735 darkFramesModel->removeRow(row);
736 darkFramesModel->submitAll();
737 userdb.close();
738
739 darkTableView->selectionModel()->select(darkFramesModel->index(row - 1, 0), QItemSelectionModel::ClearAndSelect);
740
741 refreshFromDB();
742 reloadDarksFromDatabase();
743 }
744
745 ///////////////////////////////////////////////////////////////////////////////////////
746 ///
747 ///////////////////////////////////////////////////////////////////////////////////////
openDarksFolder()748 void DarkLibrary::openDarksFolder()
749 {
750 QString darkFilesPath = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("darks");
751
752 QDesktopServices::openUrl(QUrl::fromLocalFile(darkFilesPath));
753 }
754
755 ///////////////////////////////////////////////////////////////////////////////////////
756 ///
757 ///////////////////////////////////////////////////////////////////////////////////////
refreshDefectMastersList(const QString & camera)758 void DarkLibrary::refreshDefectMastersList(const QString &camera)
759 {
760 if (darkFramesModel->rowCount() == 0)
761 return;
762
763 masterDarksCombo->blockSignals(true);
764 masterDarksCombo->clear();
765
766 for (int i = 0; i < darkFramesModel->rowCount(); ++i)
767 {
768 QSqlRecord record = darkFramesModel->record(i);
769
770 if (record.value("ccd") != camera)
771 continue;
772
773 int binX = record.value("binX").toInt();
774 int binY = record.value("binY").toInt();
775 double temperature = record.value("temperature").toDouble();
776 double duration = record.value("duration").toDouble();
777 QString ts = record.value("timestamp").toString();
778
779 QString entry = QString("%1 secs %2x%3")
780 .arg(QString::number(duration, 'f', 1))
781 .arg(QString::number(binX))
782 .arg(QString::number(binY));
783
784 if (temperature > INVALID_VALUE)
785 entry.append(QString(" @ %1°").arg(QString::number(temperature, 'f', 1)));
786
787 masterDarksCombo->addItem(entry);
788 }
789
790 masterDarksCombo->blockSignals(false);
791
792 //loadDefectMap();
793
794 }
795 ///////////////////////////////////////////////////////////////////////////////////////
796 ///
797 ///////////////////////////////////////////////////////////////////////////////////////
reloadDarksFromDatabase()798 void DarkLibrary::reloadDarksFromDatabase()
799 {
800 QSqlDatabase userdb = QSqlDatabase::database("userdb");
801 userdb.open();
802
803 const QString camera = m_CurrentCamera->getDeviceName();
804
805 delete (darkFramesModel);
806 delete (sortFilter);
807
808 darkFramesModel = new QSqlTableModel(this, userdb);
809 darkFramesModel->setTable("darkframe");
810 darkFramesModel->setFilter(QString("ccd='%1'").arg(camera));
811 darkFramesModel->select();
812
813 sortFilter = new QSortFilterProxyModel(this);
814 sortFilter->setSourceModel(darkFramesModel);
815 sortFilter->sort (0);
816 darkTableView->setModel (sortFilter);
817
818 //darkTableView->setModel(darkFramesModel);
819 // Hide ID
820 darkTableView->hideColumn(0);
821 // Hide Chip
822 darkTableView->hideColumn(2);
823
824 userdb.close();
825
826 if (darkFramesModel->rowCount() == 0 && m_CurrentDarkFrame)
827 {
828 clearBuffers();
829 return;
830 }
831
832 refreshDefectMastersList(camera);
833 loadCurrentMasterDark(camera);
834 }
835
836 ///////////////////////////////////////////////////////////////////////////////////////
837 ///
838 ///////////////////////////////////////////////////////////////////////////////////////
loadCurrentMasterDark(const QString & camera,int masterIndex)839 void DarkLibrary::loadCurrentMasterDark(const QString &camera, int masterIndex)
840 {
841 // Do not process empty models
842 if (darkFramesModel->rowCount() == 0)
843 return;
844
845 if (masterIndex == -1)
846 masterIndex = masterDarksCombo->currentIndex();
847
848 if (masterIndex < 0 || masterIndex >= darkFramesModel->rowCount())
849 return;
850
851 QSqlRecord record = darkFramesModel->record(masterIndex);
852 if (record.value("ccd") != camera)
853 return;
854 // Get the master dark frame file name
855 m_MasterDarkFrameFilename = record.value("filename").toString();
856
857 if (m_MasterDarkFrameFilename.isEmpty())
858 return;
859
860 // Get defect file name as well if available.
861 m_DefectMapFilename = record.value("defectmap").toString();
862
863 // If current dark frame is different from target filename, then load from file
864 if (m_CurrentDarkFrame->filename() != m_MasterDarkFrameFilename)
865 m_DarkFrameFutureWatcher.setFuture(m_CurrentDarkFrame->loadFromFile(m_MasterDarkFrameFilename));
866 // If current dark frame is the same one loaded, then check if we need to reload defect map
867 else
868 loadCurrentMasterDefectMap();
869 }
870
871 ///////////////////////////////////////////////////////////////////////////////////////
872 ///
873 ///////////////////////////////////////////////////////////////////////////////////////
loadCurrentMasterDefectMap()874 void DarkLibrary::loadCurrentMasterDefectMap()
875 {
876 // Find if we have an existing map
877 if (m_CachedDefectMaps.contains(m_MasterDarkFrameFilename))
878 {
879 if (m_CurrentDefectMap != m_CachedDefectMaps.value(m_MasterDarkFrameFilename))
880 {
881 m_CurrentDefectMap = m_CachedDefectMaps.value(m_MasterDarkFrameFilename);
882 m_DarkView->setDefectMap(m_CurrentDefectMap);
883 m_CurrentDefectMap->setDarkData(m_CurrentDarkFrame);
884 }
885 }
886 // Create new defect map
887 else
888 {
889 m_CurrentDefectMap.reset(new DefectMap());
890 connect(m_CurrentDefectMap.data(), &DefectMap::pixelsUpdated, [this](uint32_t hot, uint32_t cold)
891 {
892 hotPixelsCount->setValue(hot);
893 coldPixelsCount->setValue(cold);
894 aggresivenessHotSlider->setValue(m_CurrentDefectMap->property("HotPixelAggressiveness").toInt());
895 aggresivenessColdSlider->setValue(m_CurrentDefectMap->property("ColdPixelAggressiveness").toInt());
896 });
897
898 if (!m_DefectMapFilename.isEmpty())
899 cacheDefectMapFromFile(m_MasterDarkFrameFilename, m_DefectMapFilename);
900
901 m_DarkView->setDefectMap(m_CurrentDefectMap);
902 m_CurrentDefectMap->setDarkData(m_CurrentDarkFrame);
903 }
904 }
905
906 ///////////////////////////////////////////////////////////////////////////////////////
907 ///
908 ///////////////////////////////////////////////////////////////////////////////////////
populateMasterMetedata()909 void DarkLibrary::populateMasterMetedata()
910 {
911 if (m_CurrentDarkFrame.isNull())
912 return;
913
914 QVariant value;
915 // TS
916 if (m_CurrentDarkFrame->getRecordValue("DATE-OBS", value))
917 masterTime->setText(value.toString());
918 // Temperature
919 if (m_CurrentDarkFrame->getRecordValue("CCD-TEMP", value) && value.toDouble() < 100)
920 masterTemperature->setText(QString::number(value.toDouble(), 'f', 1));
921 // Exposure
922 if (m_CurrentDarkFrame->getRecordValue("EXPTIME", value))
923 masterExposure->setText(value.toString());
924 // Median
925 {
926 double median = m_CurrentDarkFrame->getAverageMedian();
927 if (median > 0)
928 masterMedian->setText(QString::number(median, 'f', 1));
929 }
930 // Mean
931 {
932 double mean = m_CurrentDarkFrame->getAverageMean();
933 masterMean->setText(QString::number(mean, 'f', 1));
934 }
935 // Standard Deviation
936 {
937 double stddev = m_CurrentDarkFrame->getAverageStdDev();
938 masterDeviation->setText(QString::number(stddev, 'f', 1));
939 }
940 }
941
942 ///////////////////////////////////////////////////////////////////////////////////////
943 ///
944 ///////////////////////////////////////////////////////////////////////////////////////
loadDarkFITS(QModelIndex index)945 void DarkLibrary::loadDarkFITS(QModelIndex index)
946 {
947 QSqlRecord record = darkFramesModel->record(index.row());
948
949 QString filename = record.value("filename").toString();
950
951 if (filename.isEmpty() == false)
952 m_DarkView->loadFile(filename);
953 }
954
955 ///////////////////////////////////////////////////////////////////////////////////////
956 ///
957 ///////////////////////////////////////////////////////////////////////////////////////
addCamera(ISD::GDInterface * newCCD)958 void DarkLibrary::addCamera(ISD::GDInterface * newCCD)
959 {
960 m_Cameras.append(static_cast<ISD::CCD*>(newCCD));
961 cameraS->addItem(newCCD->getDeviceName());
962
963 checkCamera();
964
965 reloadDarksFromDatabase();
966 }
967
968 ///////////////////////////////////////////////////////////////////////////////////////
969 ///
970 ///////////////////////////////////////////////////////////////////////////////////////
removeCamera(ISD::GDInterface * newCCD)971 void DarkLibrary::removeCamera(ISD::GDInterface * newCCD)
972 {
973 m_Cameras.removeOne(static_cast<ISD::CCD*>(newCCD));
974 cameraS->removeItem(cameraS->findText(newCCD->getDeviceName()));
975 }
976
977 ///////////////////////////////////////////////////////////////////////////////////////
978 ///
979 ///////////////////////////////////////////////////////////////////////////////////////
checkCamera(int ccdNum)980 void DarkLibrary::checkCamera(int ccdNum)
981 {
982 if (ccdNum == -1)
983 {
984 ccdNum = cameraS->currentIndex();
985
986 if (ccdNum == -1)
987 return;
988 }
989
990 if (ccdNum < m_Cameras.count())
991 {
992 // Check whether main camera or guide head only
993 m_CurrentCamera = m_Cameras.at(ccdNum);
994
995 const QString device = m_CurrentCamera->getDeviceName();
996 if (m_DarkCameras.contains(device))
997 preferDarksRadio->setChecked(true);
998 else if (m_DefectCameras.contains(device))
999 preferDefectsRadio->setChecked(true);
1000
1001 m_TargetChip = nullptr;
1002 if (cameraS->itemText(ccdNum).right(6) == QString("Guider"))
1003 {
1004 m_UseGuideHead = true;
1005 m_TargetChip = m_CurrentCamera->getChip(ISD::CCDChip::GUIDE_CCD);
1006 }
1007
1008 if (m_TargetChip == nullptr)
1009 {
1010 m_UseGuideHead = false;
1011 m_TargetChip = m_CurrentCamera->getChip(ISD::CCDChip::PRIMARY_CCD);
1012 }
1013
1014 // Make sure we have a valid chip and valid base device.
1015 // Make sure we are not in capture process.
1016 if (!m_TargetChip || !m_TargetChip->getCCD() || !m_TargetChip->getCCD()->getBaseDevice() ||
1017 m_TargetChip->isCapturing())
1018 return;
1019
1020 // for (auto &ccd : m_Cameras)
1021 // {
1022 // disconnect(ccd, &ISD::CCD::numberUpdated, this, &Ekos::Capture::processCCDNumber);
1023 // disconnect(ccd, &ISD::CCD::newTemperatureValue, this, &Ekos::Capture::updateCCDTemperature);
1024 // disconnect(ccd, &ISD::CCD::coolerToggled, this, &Ekos::Capture::setCoolerToggled);
1025 // disconnect(ccd, &ISD::CCD::newRemoteFile, this, &Ekos::Capture::setNewRemoteFile);
1026 // disconnect(ccd, &ISD::CCD::videoStreamToggled, this, &Ekos::Capture::setVideoStreamEnabled);
1027 // disconnect(ccd, &ISD::CCD::ready, this, &Ekos::Capture::ready);
1028 // }
1029
1030 if (m_CurrentCamera->hasCoolerControl())
1031 {
1032 temperatureLabel->setEnabled(true);
1033 temperatureStepLabel->setEnabled(true);
1034 temperatureToLabel->setEnabled(true);
1035 temperatureStepSpin->setEnabled(true);
1036 minTemperatureSpin->setEnabled(true);
1037 maxTemperatureSpin->setEnabled(true);
1038
1039 }
1040 else
1041 {
1042 temperatureLabel->setEnabled(false);
1043 temperatureStepLabel->setEnabled(false);
1044 temperatureToLabel->setEnabled(false);
1045 temperatureStepSpin->setEnabled(false);
1046 minTemperatureSpin->setEnabled(false);
1047 maxTemperatureSpin->setEnabled(false);
1048 }
1049
1050 countDarkTotalTime();
1051 }
1052 }
1053
1054 ///////////////////////////////////////////////////////////////////////////////////////
1055 ///
1056 ///////////////////////////////////////////////////////////////////////////////////////
countDarkTotalTime()1057 void DarkLibrary::countDarkTotalTime()
1058 {
1059
1060 //double exposureCount = (maxExposureSpin->value() - minExposureSpin->value()) / exposureStepSin->value();
1061 double temperatureCount = 1;
1062 if (m_CurrentCamera->hasCoolerControl() && std::fabs(maxTemperatureSpin->value() - minTemperatureSpin->value()) > 0)
1063 temperatureCount = (std::fabs((maxTemperatureSpin->value() - minTemperatureSpin->value())) / temperatureStepSpin->value()) +
1064 1;
1065 int binnings = 0;
1066 if (bin1Check->isChecked())
1067 binnings++;
1068 if (bin2Check->isChecked())
1069 binnings++;
1070 if (bin4Check->isChecked())
1071 binnings++;
1072
1073 double darkTime = 0;
1074 int imagesCount = 0;
1075 for (double startExposure = minExposureSpin->value(); startExposure <= maxExposureSpin->value();
1076 startExposure += exposureStepSin->value())
1077 {
1078 darkTime += startExposure * temperatureCount * binnings * countSpin->value();
1079 imagesCount += temperatureCount * binnings * countSpin->value();
1080 }
1081
1082 totalTime->setText(QString::number(darkTime / 60.0, 'f', 1));
1083 totalImages->setText(QString::number(imagesCount));
1084 darkProgress->setMaximum(imagesCount);
1085
1086 }
1087
1088 ///////////////////////////////////////////////////////////////////////////////////////
1089 ///
1090 ///////////////////////////////////////////////////////////////////////////////////////
generateDarkJobs()1091 void DarkLibrary::generateDarkJobs()
1092 {
1093 // Always clear sequence queue before starting
1094 m_CaptureModule->clearSequenceQueue();
1095
1096 if (m_JobsGenerated == false)
1097 {
1098 m_JobsGenerated = true;
1099 m_PresetSettings = m_CaptureModule->getPresetSettings();
1100 }
1101
1102 QList<double> temperatures;
1103 if (m_CurrentCamera->hasCoolerControl() && std::fabs(maxTemperatureSpin->value() - minTemperatureSpin->value()) >= 0)
1104 {
1105 for (double oneTemperature = minTemperatureSpin->value(); oneTemperature <= maxTemperatureSpin->value();
1106 oneTemperature += temperatureStepSpin->value())
1107 {
1108 temperatures << oneTemperature;
1109 }
1110
1111 // Enforce temperature set
1112 m_CaptureModule->setForceTemperature(true);
1113 }
1114 else
1115 {
1116 // Disable temperature set
1117 m_CaptureModule->setForceTemperature(false);
1118 temperatures << INVALID_VALUE;
1119 }
1120
1121 QList<uint8_t> bins;
1122 if (bin1Check->isChecked())
1123 bins << 1;
1124 if (bin2Check->isChecked())
1125 bins << 2;
1126 if (bin4Check->isChecked())
1127 bins << 4;
1128
1129 QList<double> exposures;
1130 for (double oneExposure = minExposureSpin->value(); oneExposure <= maxExposureSpin->value();
1131 oneExposure += exposureStepSin->value())
1132 {
1133 exposures << oneExposure;
1134 }
1135
1136 QString prefix = QDir::tempPath() + QDir::separator() + QString::number(QDateTime::currentSecsSinceEpoch()) +
1137 QDir::separator();
1138
1139
1140 int sequence = 0;
1141 for (const auto oneTemperature : qAsConst(temperatures))
1142 {
1143 for (const auto &oneExposure : qAsConst(exposures))
1144 {
1145 for (const auto &oneBin : qAsConst(bins))
1146 {
1147 sequence++;
1148
1149 QJsonObject settings;
1150
1151 settings["camera"] = cameraS->currentText();
1152 settings["exp"] = oneExposure;
1153 settings["bin"] = oneBin;
1154 settings["frameType"] = FRAME_DARK;
1155 settings["temperature"] = oneTemperature;
1156 settings["format"] = 0;
1157
1158 QString directory = prefix + QString("sequence_%1").arg(sequence);
1159 QJsonObject fileSettings;
1160
1161 fileSettings["directory"] = directory;
1162 m_CaptureModule->setPresetSettings(settings);
1163 m_CaptureModule->setFileSettings(fileSettings);
1164 m_CaptureModule->setCount(countSpin->value());
1165 m_CaptureModule->addJob();
1166 }
1167 }
1168 }
1169 }
1170
1171 ///////////////////////////////////////////////////////////////////////////////////////
1172 ///
1173 ///////////////////////////////////////////////////////////////////////////////////////
executeDarkJobs()1174 void DarkLibrary::executeDarkJobs()
1175 {
1176 m_DarkImagesCounter = 0;
1177 darkProgress->setValue(0);
1178 darkProgress->setTextVisible(true);
1179 connect(m_CaptureModule, &Capture::newImage, this, &DarkLibrary::processNewImage, Qt::UniqueConnection);
1180 connect(m_CaptureModule, &Capture::newStatus, this, &DarkLibrary::setCaptureState, Qt::UniqueConnection);
1181 connect(m_CurrentCamera, &ISD::CCD::BLOBUpdated, this, &DarkLibrary::processNewBLOB, Qt::UniqueConnection);
1182
1183 Options::setUseFITSViewer(false);
1184 Options::setUseSummaryPreview(false);
1185 startB->setEnabled(false);
1186 stopB->setEnabled(true);
1187 m_DarkView->reset();
1188 m_StatusLabel->setText(i18n("In progress..."));
1189 m_CaptureModule->start();
1190
1191 }
1192
1193 ///////////////////////////////////////////////////////////////////////////////////////
1194 ///
1195 ///////////////////////////////////////////////////////////////////////////////////////
stopDarkJobs()1196 void DarkLibrary::stopDarkJobs()
1197 {
1198 m_CaptureModule->abort();
1199 darkProgress->setValue(0);
1200 m_DarkView->reset();
1201 }
1202
1203 ///////////////////////////////////////////////////////////////////////////////////////
1204 ///
1205 ///////////////////////////////////////////////////////////////////////////////////////
initView()1206 void DarkLibrary::initView()
1207 {
1208 m_DarkView = new DarkView(darkWidget);
1209 m_DarkView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1210 m_DarkView->setBaseSize(darkWidget->size());
1211 m_DarkView->createFloatingToolBar();
1212 QVBoxLayout *vlayout = new QVBoxLayout();
1213 vlayout->addWidget(m_DarkView);
1214 darkWidget->setLayout(vlayout);
1215 //connect(m_DarkView, &FITSView::loaded, this, &DarkLibrary::loadCurrentMasterDefectMap);
1216 }
1217
1218 ///////////////////////////////////////////////////////////////////////////////////////
1219 ///
1220 ///////////////////////////////////////////////////////////////////////////////////////
aggregate(const QSharedPointer<FITSData> & data)1221 void DarkLibrary::aggregate(const QSharedPointer<FITSData> &data)
1222 {
1223 switch (data->dataType())
1224 {
1225 case TBYTE:
1226 aggregateInternal<uint8_t>(data);
1227 break;
1228
1229 case TSHORT:
1230 aggregateInternal<int16_t>(data);
1231 break;
1232
1233 case TUSHORT:
1234 aggregateInternal<uint16_t>(data);
1235 break;
1236
1237 case TLONG:
1238 aggregateInternal<int32_t>(data);
1239 break;
1240
1241 case TULONG:
1242 aggregateInternal<uint32_t>(data);
1243 break;
1244
1245 case TFLOAT:
1246 aggregateInternal<float>(data);
1247 break;
1248
1249 case TLONGLONG:
1250 aggregateInternal<int64_t>(data);
1251 break;
1252
1253 case TDOUBLE:
1254 aggregateInternal<double>(data);
1255 break;
1256
1257 default:
1258 break;
1259 }
1260 }
1261
1262 ///////////////////////////////////////////////////////////////////////////////////////
1263 ///
1264 ///////////////////////////////////////////////////////////////////////////////////////
1265 template <typename T>
aggregateInternal(const QSharedPointer<FITSData> & data)1266 void DarkLibrary::aggregateInternal(const QSharedPointer<FITSData> &data)
1267 {
1268 T const *darkBuffer = reinterpret_cast<T const*>(data->getImageBuffer());
1269 for (uint32_t i = 0; i < m_DarkMasterBuffer.size(); i++)
1270 m_DarkMasterBuffer[i] += darkBuffer[i];
1271 }
1272
1273 ///////////////////////////////////////////////////////////////////////////////////////
1274 ///
1275 ///////////////////////////////////////////////////////////////////////////////////////
generateMasterFrame(const QSharedPointer<FITSData> & data,const QJsonObject & metadata)1276 void DarkLibrary::generateMasterFrame(const QSharedPointer<FITSData> &data, const QJsonObject &metadata)
1277 {
1278 switch (data->dataType())
1279 {
1280 case TBYTE:
1281 generateMasterFrameInternal<uint8_t>(data, metadata);
1282 break;
1283
1284 case TSHORT:
1285 generateMasterFrameInternal<int16_t>(data, metadata);
1286 break;
1287
1288 case TUSHORT:
1289 generateMasterFrameInternal<uint16_t>(data, metadata);
1290 break;
1291
1292 case TLONG:
1293 generateMasterFrameInternal<int32_t>(data, metadata);
1294 break;
1295
1296 case TULONG:
1297 generateMasterFrameInternal<uint32_t>(data, metadata);
1298 break;
1299
1300 case TFLOAT:
1301 generateMasterFrameInternal<float>(data, metadata);
1302 break;
1303
1304 case TLONGLONG:
1305 generateMasterFrameInternal<int64_t>(data, metadata);
1306 break;
1307
1308 case TDOUBLE:
1309 generateMasterFrameInternal<double>(data, metadata);
1310 break;
1311
1312 default:
1313 break;
1314 }
1315
1316 // Reset Master Buffer
1317 m_DarkMasterBuffer.assign(m_DarkMasterBuffer.size(), 0);
1318 }
1319
1320 ///////////////////////////////////////////////////////////////////////////////////////
1321 ///
1322 ///////////////////////////////////////////////////////////////////////////////////////
generateMasterFrameInternal(const QSharedPointer<FITSData> & data,const QJsonObject & metadata)1323 template <typename T> void DarkLibrary::generateMasterFrameInternal(const QSharedPointer<FITSData> &data,
1324 const QJsonObject &metadata)
1325 {
1326 T *writableBuffer = reinterpret_cast<T *>(data->getWritableImageBuffer());
1327 const uint32_t count = metadata["count"].toInt();
1328 // Average the values
1329 for (uint32_t i = 0; i < m_DarkMasterBuffer.size(); i++)
1330 writableBuffer[i] = m_DarkMasterBuffer[i] / count;
1331
1332
1333 QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
1334 QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("darks/darkframe_" + ts + ".fits");
1335
1336 data->calculateStats(true);
1337 if (!data->saveImage(path))
1338 {
1339 m_FileLabel->setText(i18n("Failed to save master frame: %1", data->getLastError()));
1340 return;
1341 }
1342
1343 m_CachedDarkFrames[path] = data;
1344
1345 QVariantMap map;
1346 map["ccd"] = metadata["camera"].toString();
1347 map["chip"] = metadata["chip"].toInt();
1348 map["binX"] = metadata["binx"].toInt();
1349 map["binY"] = metadata["biny"].toInt();
1350 map["temperature"] = metadata["temperature"].toDouble(INVALID_VALUE);
1351 map["duration"] = metadata["duration"].toDouble();
1352 map["filename"] = path;
1353
1354 m_DarkFramesDatabaseList.append(map);
1355 m_FileLabel->setText(i18n("Master Dark saved to %1", path));
1356 KStarsData::Instance()->userdb()->AddDarkFrame(map);
1357 }
1358
1359 ///////////////////////////////////////////////////////////////////////////////////////
1360 ///
1361 ///////////////////////////////////////////////////////////////////////////////////////
setCaptureModule(Capture * instance)1362 void DarkLibrary::setCaptureModule(Capture *instance)
1363 {
1364 m_CaptureModule = instance;
1365 m_RememberFITSDirectory = m_CaptureModule->fileDirectoryT->text();
1366 }
1367
1368 ///////////////////////////////////////////////////////////////////////////////////////
1369 ///
1370 ///////////////////////////////////////////////////////////////////////////////////////
setCaptureState(CaptureState state)1371 void DarkLibrary::setCaptureState(CaptureState state)
1372 {
1373 switch (state)
1374 {
1375 case CAPTURE_ABORTED:
1376 setCompleted();
1377 m_StatusLabel->setText(i18n("Capture aborted."));
1378 break;
1379 case CAPTURE_COMPLETE:
1380 setCompleted();
1381 m_StatusLabel->setText(i18n("Capture completed."));
1382 break;
1383 default:
1384 break;
1385 }
1386 }
1387
1388 ///////////////////////////////////////////////////////////////////////////////////////
1389 ///
1390 ///////////////////////////////////////////////////////////////////////////////////////
saveDefectMap()1391 void DarkLibrary::saveDefectMap()
1392 {
1393 if (!m_CurrentDarkFrame)
1394 return;
1395
1396 QString filename = m_CurrentDefectMap->filename();
1397 bool newFile = false;
1398 if (filename.isEmpty())
1399 {
1400 QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
1401 filename = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("defectmaps/defectmap_" + ts +
1402 ".json");
1403 newFile = true;
1404 }
1405
1406 if (m_CurrentDefectMap->save(filename, m_CurrentCamera->getDeviceName()))
1407 {
1408 m_FileLabel->setText(i18n("Defect map saved to %1", filename));
1409
1410 if (newFile)
1411 {
1412 auto currentMap = std::find_if(m_DarkFramesDatabaseList.begin(),
1413 m_DarkFramesDatabaseList.end(), [&](const QVariantMap & oneMap)
1414 {
1415 return oneMap["filename"].toString() == m_CurrentDarkFrame->filename();
1416 });
1417
1418 if (currentMap != m_DarkFramesDatabaseList.end())
1419 {
1420 (*currentMap)["defectmap"] = filename;
1421 KStarsData::Instance()->userdb()->UpdateDarkFrame(*currentMap);
1422 }
1423 }
1424 }
1425 else
1426 {
1427 m_FileLabel->setText(i18n("Failed to save defect map to %1", filename));
1428 }
1429 }
1430
1431 }
1432
1433