File indexing completed on 2024-04-28 03:43:06
0001 /* 0002 SPDX-FileCopyrightText: 2016 Jasem Mutlaq <mutlaqja@ikarustech.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "darklibrary.h" 0008 #include "Options.h" 0009 0010 #include "ekos/manager.h" 0011 #include "ekos/capture/capture.h" 0012 #include "ekos/capture/sequencejob.h" 0013 #include "ekos/auxiliary/opticaltrainmanager.h" 0014 #include "ekos/auxiliary/profilesettings.h" 0015 #include "ekos/auxiliary/opticaltrainsettings.h" 0016 #include "kstars.h" 0017 #include "kspaths.h" 0018 #include "kstarsdata.h" 0019 #include "fitsviewer/fitsdata.h" 0020 #include "fitsviewer/fitsview.h" 0021 0022 #include <QDesktopServices> 0023 #include <QSqlRecord> 0024 #include <QSqlTableModel> 0025 #include <QStatusBar> 0026 #include <algorithm> 0027 #include <array> 0028 0029 namespace Ekos 0030 { 0031 DarkLibrary *DarkLibrary::_DarkLibrary = nullptr; 0032 0033 DarkLibrary *DarkLibrary::Instance() 0034 { 0035 if (_DarkLibrary == nullptr) 0036 _DarkLibrary = new DarkLibrary(Manager::Instance()); 0037 0038 return _DarkLibrary; 0039 } 0040 0041 DarkLibrary::DarkLibrary(QWidget *parent) : QDialog(parent) 0042 { 0043 setupUi(this); 0044 0045 m_StatusBar = new QStatusBar(this); 0046 m_StatusLabel = new QLabel(i18n("Idle"), this); 0047 m_FileLabel = new QLabel(this); 0048 m_FileLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter); 0049 0050 m_StatusBar->insertPermanentWidget(0, m_StatusLabel); 0051 m_StatusBar->insertPermanentWidget(1, m_FileLabel, 1); 0052 mainLayout->addWidget(m_StatusBar); 0053 0054 QDir writableDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); 0055 writableDir.mkpath("darks"); 0056 writableDir.mkpath("defectmaps"); 0057 0058 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 0059 // Dark Generation Connections 0060 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 0061 m_CurrentDarkFrame.reset(new FITSData(), &QObject::deleteLater); 0062 0063 connect(darkTableView, &QAbstractItemView::doubleClicked, this, [this](QModelIndex index) 0064 { 0065 loadIndexInView(index.row()); 0066 }); 0067 connect(openDarksFolderB, &QPushButton::clicked, this, &DarkLibrary::openDarksFolder); 0068 connect(clearAllB, &QPushButton::clicked, this, &DarkLibrary::clearAll); 0069 connect(clearRowB, &QPushButton::clicked, this, &DarkLibrary::clearRow); 0070 connect(clearExpiredB, &QPushButton::clicked, this, &DarkLibrary::clearExpired); 0071 connect(refreshB, &QPushButton::clicked, this, &DarkLibrary::reloadDarksFromDatabase); 0072 0073 connect(&m_DarkFrameFutureWatcher, &QFutureWatcher<bool>::finished, this, [this]() 0074 { 0075 // If loading is successful, then set it in current dark view 0076 if (m_DarkFrameFutureWatcher.result()) 0077 { 0078 m_DarkView->loadData(m_CurrentDarkFrame); 0079 loadCurrentMasterDefectMap(); 0080 populateMasterMetedata(); 0081 } 0082 else 0083 m_FileLabel->setText(i18n("Failed to load %1: %2", m_MasterDarkFrameFilename, m_CurrentDarkFrame->getLastError())); 0084 0085 }); 0086 0087 connect(masterDarksCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this](int index) 0088 { 0089 if (m_Camera) 0090 DarkLibrary::loadCurrentMasterDark(m_Camera->getDeviceName(), index); 0091 }); 0092 0093 connect(minExposureSpin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime); 0094 connect(maxExposureSpin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime); 0095 connect(exposureStepSin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime); 0096 0097 connect(minTemperatureSpin, &QDoubleSpinBox::editingFinished, this, [this]() 0098 { 0099 maxTemperatureSpin->setMinimum(minTemperatureSpin->value()); 0100 countDarkTotalTime(); 0101 }); 0102 connect(maxTemperatureSpin, &QDoubleSpinBox::editingFinished, this, [this]() 0103 { 0104 minTemperatureSpin->setMaximum(maxTemperatureSpin->value()); 0105 countDarkTotalTime(); 0106 }); 0107 connect(temperatureStepSpin, &QDoubleSpinBox::editingFinished, this, [this]() 0108 { 0109 maxTemperatureSpin->setMinimum(minTemperatureSpin->value()); 0110 minTemperatureSpin->setMaximum(maxTemperatureSpin->value()); 0111 countDarkTotalTime(); 0112 }); 0113 0114 connect(countSpin, &QDoubleSpinBox::editingFinished, this, &DarkLibrary::countDarkTotalTime); 0115 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 0116 connect(binningButtonGroup, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::buttonToggled), 0117 this, [this](int, bool) 0118 #else 0119 connect(binningButtonGroup, static_cast<void (QButtonGroup::*)(int, bool)>(&QButtonGroup::idToggled), 0120 this, [this](int, bool) 0121 #endif 0122 { 0123 countDarkTotalTime(); 0124 }); 0125 0126 connect(startB, &QPushButton::clicked, this, &DarkLibrary::start); 0127 connect(stopB, &QPushButton::clicked, this, &DarkLibrary::stop); 0128 0129 KStarsData::Instance()->userdb()->GetAllDarkFrames(m_DarkFramesDatabaseList); 0130 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 0131 // Defect Map Connections 0132 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 0133 connect(darkTabsWidget, &QTabWidget::currentChanged, this, [this](int index) 0134 { 0135 m_DarkView->setDefectMapEnabled(index == 1 && m_CurrentDefectMap); 0136 }); 0137 connect(aggresivenessHotSlider, &QSlider::valueChanged, aggresivenessHotSpin, &QSpinBox::setValue); 0138 connect(aggresivenessColdSlider, &QSlider::valueChanged, aggresivenessColdSpin, &QSpinBox::setValue); 0139 connect(hotPixelsEnabled, &QCheckBox::toggled, this, [this](bool toggled) 0140 { 0141 if (m_CurrentDefectMap) 0142 m_CurrentDefectMap->setProperty("HotEnabled", toggled); 0143 }); 0144 connect(coldPixelsEnabled, &QCheckBox::toggled, this, [this](bool toggled) 0145 { 0146 if (m_CurrentDefectMap) 0147 m_CurrentDefectMap->setProperty("ColdEnabled", toggled); 0148 }); 0149 connect(generateMapB, &QPushButton::clicked, this, [this]() 0150 { 0151 if (m_CurrentDefectMap) 0152 { 0153 m_CurrentDefectMap->setProperty("HotPixelAggressiveness", aggresivenessHotSpin->value()); 0154 m_CurrentDefectMap->setProperty("ColdPixelAggressiveness", aggresivenessColdSpin->value()); 0155 m_CurrentDefectMap->filterPixels(); 0156 emit newFrame(m_DarkView); 0157 } 0158 }); 0159 connect(resetMapParametersB, &QPushButton::clicked, this, [this]() 0160 { 0161 if (m_CurrentDefectMap) 0162 { 0163 aggresivenessHotSlider->setValue(75); 0164 aggresivenessColdSlider->setValue(75); 0165 m_CurrentDefectMap->setProperty("HotPixelAggressiveness", 75); 0166 m_CurrentDefectMap->setProperty("ColdPixelAggressiveness", 75); 0167 m_CurrentDefectMap->filterPixels(); 0168 } 0169 }); 0170 connect(saveMapB, &QPushButton::clicked, this, &DarkLibrary::saveDefectMap); 0171 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 0172 // Settings & Initialization 0173 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 0174 m_RememberFITSViewer = Options::useFITSViewer(); 0175 m_RememberSummaryView = Options::useSummaryPreview(); 0176 initView(); 0177 0178 loadGlobalSettings(); 0179 0180 connectSettings(); 0181 0182 setupOpticalTrainManager(); 0183 } 0184 0185 DarkLibrary::~DarkLibrary() 0186 { 0187 } 0188 0189 /////////////////////////////////////////////////////////////////////////////////////// 0190 /// 0191 /////////////////////////////////////////////////////////////////////////////////////// 0192 void DarkLibrary::refreshFromDB() 0193 { 0194 KStarsData::Instance()->userdb()->GetAllDarkFrames(m_DarkFramesDatabaseList); 0195 } 0196 0197 /////////////////////////////////////////////////////////////////////////////////////// 0198 /// 0199 /////////////////////////////////////////////////////////////////////////////////////// 0200 bool DarkLibrary::findDarkFrame(ISD::CameraChip *m_TargetChip, double duration, QSharedPointer<FITSData> &darkData) 0201 { 0202 QVariantMap bestCandidate; 0203 for (auto &map : m_DarkFramesDatabaseList) 0204 { 0205 // First check CCD name matches and check if we are on the correct chip 0206 if (map["ccd"].toString() == m_TargetChip->getCCD()->getDeviceName() && 0207 map["chip"].toInt() == static_cast<int>(m_TargetChip->getType())) 0208 { 0209 // Match Gain 0210 int gain = getGain(); 0211 if (gain >= 0 && map["gain"].toInt() != gain) 0212 continue; 0213 0214 // Match ISO 0215 QString isoValue; 0216 if (m_TargetChip->getISOValue(isoValue) && map["iso"].toString() != isoValue) 0217 continue; 0218 0219 // Match binning 0220 int binX = 1, binY = 1; 0221 m_TargetChip->getBinning(&binX, &binY); 0222 0223 // Then check if binning is the same 0224 if (map["binX"].toInt() != binX || map["binY"].toInt() != binY) 0225 continue; 0226 0227 // If camera has an active cooler, then we check temperature against the absolute threshold. 0228 if (m_TargetChip->getCCD()->hasCoolerControl()) 0229 { 0230 double temperature = 0; 0231 m_TargetChip->getCCD()->getTemperature(&temperature); 0232 double darkTemperature = map["temperature"].toDouble(); 0233 // If different is above threshold, it is completely rejected. 0234 if (darkTemperature != INVALID_VALUE && fabs(darkTemperature - temperature) > maxDarkTemperatureDiff->value()) 0235 continue; 0236 } 0237 0238 if (bestCandidate.isEmpty()) 0239 { 0240 bestCandidate = map; 0241 continue; 0242 } 0243 0244 // We try to find the best frame 0245 // Frame closest in exposure duration wins 0246 // Frame with temperature closest to stored temperature wins (if temperature is reported) 0247 uint32_t thisMapScore = 0; 0248 uint32_t bestCandidateScore = 0; 0249 0250 // Else we check for the closest passive temperature 0251 if (m_TargetChip->getCCD()->hasCooler()) 0252 { 0253 double temperature = 0; 0254 m_TargetChip->getCCD()->getTemperature(&temperature); 0255 double diffMap = std::fabs(temperature - map["temperature"].toDouble()); 0256 double diffBest = std::fabs(temperature - bestCandidate["temperature"].toDouble()); 0257 // Prefer temperatures closest to target 0258 if (diffMap < diffBest) 0259 thisMapScore++; 0260 else if (diffBest < diffMap) 0261 bestCandidateScore++; 0262 } 0263 0264 // Duration has a higher score priority over temperature 0265 { 0266 double diffMap = std::fabs(map["duration"].toDouble() - duration); 0267 double diffBest = std::fabs(bestCandidate["duration"].toDouble() - duration); 0268 if (diffMap < diffBest) 0269 thisMapScore += 5; 0270 else if (diffBest < diffMap) 0271 bestCandidateScore += 5; 0272 } 0273 0274 // More recent has a higher score than older. 0275 { 0276 const QDateTime now = QDateTime::currentDateTime(); 0277 int64_t diffMap = map["timestamp"].toDateTime().secsTo(now); 0278 int64_t diffBest = bestCandidate["timestamp"].toDateTime().secsTo(now); 0279 if (diffMap < diffBest) 0280 thisMapScore++; 0281 else if (diffBest < diffMap) 0282 bestCandidateScore++; 0283 } 0284 0285 // Find candidate with closest time in case we have multiple defect maps 0286 if (thisMapScore > bestCandidateScore) 0287 bestCandidate = map; 0288 } 0289 } 0290 0291 if (bestCandidate.isEmpty()) 0292 return false; 0293 0294 if (fabs(bestCandidate["duration"].toDouble() - duration) > 3) 0295 emit i18n("Using available dark frame with %1 seconds exposure. Please take a dark frame with %1 seconds exposure for more accurate results.", 0296 QString::number(bestCandidate["duration"].toDouble(), 'f', 1), 0297 QString::number(duration, 'f', 1)); 0298 0299 QString filename = bestCandidate["filename"].toString(); 0300 0301 // Finally check if the duration is acceptable 0302 QDateTime frameTime = bestCandidate["timestamp"].toDateTime(); 0303 if (frameTime.daysTo(QDateTime::currentDateTime()) > Options::darkLibraryDuration()) 0304 { 0305 emit i18n("Dark frame %s is expired. Please create new master dark.", filename); 0306 return false; 0307 } 0308 0309 if (m_CachedDarkFrames.contains(filename)) 0310 { 0311 darkData = m_CachedDarkFrames[filename]; 0312 return true; 0313 } 0314 0315 // Before adding to cache, clear the cache if memory drops too low. 0316 auto memoryMB = KSUtils::getAvailableRAM() / 1e6; 0317 if (memoryMB < CACHE_MEMORY_LIMIT) 0318 m_CachedDarkFrames.clear(); 0319 0320 // Finally we made it, let's put it in the hash 0321 if (cacheDarkFrameFromFile(filename)) 0322 { 0323 darkData = m_CachedDarkFrames[filename]; 0324 return true; 0325 } 0326 0327 // Remove bad dark frame 0328 emit newLog(i18n("Removing bad dark frame file %1", filename)); 0329 m_CachedDarkFrames.remove(filename); 0330 QFile::remove(filename); 0331 KStarsData::Instance()->userdb()->DeleteDarkFrame(filename); 0332 return false; 0333 0334 } 0335 0336 /////////////////////////////////////////////////////////////////////////////////////// 0337 /// 0338 /////////////////////////////////////////////////////////////////////////////////////// 0339 bool DarkLibrary::findDefectMap(ISD::CameraChip *m_TargetChip, double duration, QSharedPointer<DefectMap> &defectMap) 0340 { 0341 QVariantMap bestCandidate; 0342 for (auto &map : m_DarkFramesDatabaseList) 0343 { 0344 if (map["defectmap"].toString().isEmpty()) 0345 continue; 0346 0347 // First check CCD name matches and check if we are on the correct chip 0348 if (map["ccd"].toString() == m_TargetChip->getCCD()->getDeviceName() && 0349 map["chip"].toInt() == static_cast<int>(m_TargetChip->getType())) 0350 { 0351 int binX, binY; 0352 m_TargetChip->getBinning(&binX, &binY); 0353 0354 // Then check if binning is the same 0355 if (map["binX"].toInt() == binX && map["binY"].toInt() == binY) 0356 { 0357 if (bestCandidate.isEmpty()) 0358 { 0359 bestCandidate = map; 0360 continue; 0361 } 0362 0363 // We try to find the best frame 0364 // Frame closest in exposure duration wins 0365 // Frame with temperature closest to stored temperature wins (if temperature is reported) 0366 uint32_t thisMapScore = 0; 0367 uint32_t bestCandidateScore = 0; 0368 0369 // Else we check for the closest passive temperature 0370 if (m_TargetChip->getCCD()->hasCooler()) 0371 { 0372 double temperature = 0; 0373 m_TargetChip->getCCD()->getTemperature(&temperature); 0374 double diffMap = std::fabs(temperature - map["temperature"].toDouble()); 0375 double diffBest = std::fabs(temperature - bestCandidate["temperature"].toDouble()); 0376 // Prefer temperatures closest to target 0377 if (diffMap < diffBest) 0378 thisMapScore++; 0379 else if (diffBest < diffMap) 0380 bestCandidateScore++; 0381 } 0382 0383 // Duration has a higher score priority over temperature 0384 double diffMap = std::fabs(map["duration"].toDouble() - duration); 0385 double diffBest = std::fabs(bestCandidate["duration"].toDouble() - duration); 0386 if (diffMap < diffBest) 0387 thisMapScore += 2; 0388 else if (diffBest < diffMap) 0389 bestCandidateScore += 2; 0390 0391 // Find candidate with closest time in case we have multiple defect maps 0392 if (thisMapScore > bestCandidateScore) 0393 bestCandidate = map; 0394 } 0395 } 0396 } 0397 0398 0399 if (bestCandidate.isEmpty()) 0400 return false; 0401 0402 0403 QString darkFilename = bestCandidate["filename"].toString(); 0404 QString defectFilename = bestCandidate["defectmap"].toString(); 0405 0406 if (darkFilename.isEmpty() || defectFilename.isEmpty()) 0407 return false; 0408 0409 if (m_CachedDefectMaps.contains(darkFilename)) 0410 { 0411 defectMap = m_CachedDefectMaps[darkFilename]; 0412 return true; 0413 } 0414 0415 // Finally we made it, let's put it in the hash 0416 if (cacheDefectMapFromFile(darkFilename, defectFilename)) 0417 { 0418 defectMap = m_CachedDefectMaps[darkFilename]; 0419 return true; 0420 } 0421 else 0422 { 0423 // Remove bad dark frame 0424 emit newLog(i18n("Failed to load defect map %1", defectFilename)); 0425 return false; 0426 } 0427 } 0428 0429 /////////////////////////////////////////////////////////////////////////////////////// 0430 /// 0431 /////////////////////////////////////////////////////////////////////////////////////// 0432 bool DarkLibrary::cacheDefectMapFromFile(const QString &key, const QString &filename) 0433 { 0434 QSharedPointer<DefectMap> oneMap; 0435 oneMap.reset(new DefectMap()); 0436 0437 if (oneMap->load(filename)) 0438 { 0439 oneMap->filterPixels(); 0440 m_CachedDefectMaps[key] = oneMap; 0441 return true; 0442 } 0443 0444 emit newLog(i18n("Failed to load defect map file %1", filename)); 0445 return false; 0446 } 0447 0448 /////////////////////////////////////////////////////////////////////////////////////// 0449 /// 0450 /////////////////////////////////////////////////////////////////////////////////////// 0451 bool DarkLibrary::cacheDarkFrameFromFile(const QString &filename) 0452 { 0453 QSharedPointer<FITSData> data; 0454 data.reset(new FITSData(FITS_CALIBRATE), &QObject::deleteLater); 0455 QFuture<bool> rc = data->loadFromFile(filename); 0456 0457 rc.waitForFinished(); 0458 if (rc.result()) 0459 { 0460 m_CachedDarkFrames[filename] = data; 0461 } 0462 else 0463 { 0464 emit newLog(i18n("Failed to load dark frame file %1", filename)); 0465 } 0466 0467 return rc; 0468 } 0469 0470 /////////////////////////////////////////////////////////////////////////////////////// 0471 /// 0472 /////////////////////////////////////////////////////////////////////////////////////// 0473 void DarkLibrary::processNewImage(SequenceJob *job, const QSharedPointer<FITSData> &data) 0474 { 0475 Q_UNUSED(data) 0476 if (job->getStatus() == JOB_IDLE) 0477 return; 0478 0479 if (job->getCompleted() == job->getCoreProperty(SequenceJob::SJ_Count).toInt()) 0480 { 0481 QJsonObject metadata 0482 { 0483 {"camera", m_Camera->getDeviceName()}, 0484 {"chip", m_TargetChip->getType()}, 0485 {"binx", job->getCoreProperty(SequenceJob::SJ_Binning).toPoint().x()}, 0486 {"biny", job->getCoreProperty(SequenceJob::SJ_Binning).toPoint().y()}, 0487 {"duration", job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble()} 0488 }; 0489 0490 // Record temperature 0491 double value = 0; 0492 bool success = m_Camera->getTemperature(&value); 0493 if (success) 0494 metadata["temperature"] = value; 0495 0496 success = m_Camera->hasGain() && m_Camera->getGain(&value); 0497 if (success) 0498 metadata["gain"] = value; 0499 0500 QString isoValue; 0501 success = m_TargetChip->getISOValue(isoValue); 0502 if (success) 0503 metadata["iso"] = isoValue; 0504 0505 metadata["count"] = job->getCoreProperty(SequenceJob::SJ_Count).toInt(); 0506 generateMasterFrame(m_CurrentDarkFrame, metadata); 0507 reloadDarksFromDatabase(); 0508 populateMasterMetedata(); 0509 } 0510 } 0511 0512 /////////////////////////////////////////////////////////////////////////////////////// 0513 /// 0514 /////////////////////////////////////////////////////////////////////////////////////// 0515 void DarkLibrary::updateProperty(INDI::Property prop) 0516 { 0517 if (prop.getType() != INDI_BLOB) 0518 return; 0519 0520 auto bp = prop.getBLOB()->at(0); 0521 QByteArray buffer = QByteArray::fromRawData(reinterpret_cast<char *>(bp->getBlob()), bp->getSize()); 0522 if (!m_CurrentDarkFrame->loadFromBuffer(buffer, bp->getFormat())) 0523 { 0524 m_FileLabel->setText(i18n("Failed to process dark data.")); 0525 return; 0526 } 0527 0528 if (!m_DarkView->loadData(m_CurrentDarkFrame)) 0529 { 0530 m_FileLabel->setText(i18n("Failed to load dark data.")); 0531 return; 0532 } 0533 0534 uint32_t totalElements = m_CurrentDarkFrame->channels() * m_CurrentDarkFrame->samplesPerChannel(); 0535 if (totalElements != m_DarkMasterBuffer.size()) 0536 m_DarkMasterBuffer.assign(totalElements, 0); 0537 0538 aggregate(m_CurrentDarkFrame); 0539 darkProgress->setValue(darkProgress->value() + 1); 0540 m_StatusLabel->setText(i18n("Received %1/%2 images.", darkProgress->value(), darkProgress->maximum())); 0541 } 0542 0543 /////////////////////////////////////////////////////////////////////////////////////// 0544 /// 0545 /////////////////////////////////////////////////////////////////////////////////////// 0546 void DarkLibrary::Release() 0547 { 0548 delete (_DarkLibrary); 0549 _DarkLibrary = nullptr; 0550 0551 // m_Cameras.clear(); 0552 // cameraS->clear(); 0553 // m_CurrentCamera = nullptr; 0554 } 0555 0556 /////////////////////////////////////////////////////////////////////////////////////// 0557 /// 0558 /////////////////////////////////////////////////////////////////////////////////////// 0559 void DarkLibrary::closeEvent(QCloseEvent *ev) 0560 { 0561 Q_UNUSED(ev) 0562 Options::setUseFITSViewer(m_RememberFITSViewer); 0563 Options::setUseSummaryPreview(m_RememberSummaryView); 0564 if (m_JobsGenerated) 0565 { 0566 m_JobsGenerated = false; 0567 m_CaptureModule->clearSequenceQueue(); 0568 m_CaptureModule->setPresetSettings(m_PresetSettings); 0569 m_CaptureModule->setFileSettings(m_FileSettings); 0570 } 0571 } 0572 0573 /////////////////////////////////////////////////////////////////////////////////////// 0574 /// 0575 /////////////////////////////////////////////////////////////////////////////////////// 0576 void DarkLibrary::setCompleted() 0577 { 0578 startB->setEnabled(true); 0579 stopB->setEnabled(false); 0580 0581 Options::setUseFITSViewer(m_RememberFITSViewer); 0582 Options::setUseSummaryPreview(m_RememberSummaryView); 0583 if (m_JobsGenerated) 0584 { 0585 m_JobsGenerated = false; 0586 m_CaptureModule->clearSequenceQueue(); 0587 m_CaptureModule->setPresetSettings(m_PresetSettings); 0588 m_CaptureModule->setFileSettings(m_FileSettings); 0589 } 0590 0591 m_Camera->disconnect(this); 0592 m_CaptureModule->disconnect(this); 0593 } 0594 0595 /////////////////////////////////////////////////////////////////////////////////////// 0596 /// 0597 /////////////////////////////////////////////////////////////////////////////////////// 0598 void DarkLibrary::clearExpired() 0599 { 0600 if (darkFramesModel->rowCount() == 0) 0601 return; 0602 0603 // Anything before this must go 0604 QDateTime expiredDate = QDateTime::currentDateTime().addDays(darkLibraryDuration->value() * -1); 0605 0606 auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName()); 0607 QSqlTableModel darkframe(nullptr, userdb); 0608 darkframe.setEditStrategy(QSqlTableModel::OnManualSubmit); 0609 darkframe.setTable("darkframe"); 0610 // Select all those that already expired. 0611 darkframe.setFilter("ccd LIKE \'" + m_Camera->getDeviceName() + "\' AND timestamp < \'" + expiredDate.toString( 0612 Qt::ISODate) + "\'"); 0613 0614 darkframe.select(); 0615 0616 // Now remove all the expired files from disk 0617 for (int i = 0; i < darkframe.rowCount(); ++i) 0618 { 0619 QString oneFile = darkframe.record(i).value("filename").toString(); 0620 QFile::remove(oneFile); 0621 QString defectMap = darkframe.record(i).value("defectmap").toString(); 0622 if (defectMap.isEmpty() == false) 0623 QFile::remove(defectMap); 0624 0625 } 0626 0627 // And remove them from the database 0628 darkframe.removeRows(0, darkframe.rowCount()); 0629 darkframe.submitAll(); 0630 0631 Ekos::DarkLibrary::Instance()->refreshFromDB(); 0632 0633 reloadDarksFromDatabase(); 0634 } 0635 0636 /////////////////////////////////////////////////////////////////////////////////////// 0637 /// 0638 /////////////////////////////////////////////////////////////////////////////////////// 0639 void DarkLibrary::clearBuffers() 0640 { 0641 m_CurrentDarkFrame.clear(); 0642 // Should clear existing view 0643 m_CurrentDarkFrame.reset(new FITSData(), &QObject::deleteLater); 0644 m_DarkView->clearData(); 0645 m_CurrentDefectMap.clear(); 0646 0647 } 0648 /////////////////////////////////////////////////////////////////////////////////////// 0649 /// 0650 /////////////////////////////////////////////////////////////////////////////////////// 0651 void DarkLibrary::clearAll() 0652 { 0653 if (darkFramesModel->rowCount() == 0) 0654 return; 0655 0656 if (KMessageBox::questionYesNo(KStars::Instance(), 0657 i18n("Are you sure you want to delete all dark frames images and data?")) == 0658 KMessageBox::No) 0659 return; 0660 0661 auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName()); 0662 QSqlTableModel darkframe(nullptr, userdb); 0663 darkFramesModel->setEditStrategy(QSqlTableModel::OnManualSubmit); 0664 darkframe.setTable("darkframe"); 0665 darkframe.setFilter("ccd LIKE \'" + m_Camera->getDeviceName() + "\'"); 0666 darkFramesModel->select(); 0667 0668 // Now remove all the expired files from disk 0669 for (int i = 0; i < darkframe.rowCount(); ++i) 0670 { 0671 QString oneFile = darkframe.record(i).value("filename").toString(); 0672 QFile::remove(oneFile); 0673 QString defectMap = darkframe.record(i).value("defectmap").toString(); 0674 if (defectMap.isEmpty() == false) 0675 QFile::remove(defectMap); 0676 0677 } 0678 0679 darkFramesModel->removeRows(0, darkFramesModel->rowCount()); 0680 darkFramesModel->submitAll(); 0681 0682 Ekos::DarkLibrary::Instance()->refreshFromDB(); 0683 0684 // Refesh db entries for other cameras 0685 reloadDarksFromDatabase(); 0686 } 0687 0688 /////////////////////////////////////////////////////////////////////////////////////// 0689 /// 0690 /////////////////////////////////////////////////////////////////////////////////////// 0691 void DarkLibrary::clearRow(int index) 0692 { 0693 auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName()); 0694 if (index < 0) 0695 index = darkTableView->currentIndex().row(); 0696 0697 QSqlRecord record = darkFramesModel->record(index); 0698 QString filename = record.value("filename").toString(); 0699 QString defectMap = record.value("defectmap").toString(); 0700 QFile::remove(filename); 0701 if (!defectMap.isEmpty()) 0702 QFile::remove(defectMap); 0703 0704 darkFramesModel->removeRow(index); 0705 darkFramesModel->submitAll(); 0706 userdb.close(); 0707 0708 darkTableView->selectionModel()->select(darkFramesModel->index(index - 1, 0), QItemSelectionModel::ClearAndSelect); 0709 0710 refreshFromDB(); 0711 reloadDarksFromDatabase(); 0712 } 0713 0714 /////////////////////////////////////////////////////////////////////////////////////// 0715 /// 0716 /////////////////////////////////////////////////////////////////////////////////////// 0717 void DarkLibrary::openDarksFolder() 0718 { 0719 QString darkFilesPath = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("darks"); 0720 0721 QDesktopServices::openUrl(QUrl::fromLocalFile(darkFilesPath)); 0722 } 0723 0724 /////////////////////////////////////////////////////////////////////////////////////// 0725 /// 0726 /////////////////////////////////////////////////////////////////////////////////////// 0727 void DarkLibrary::refreshDefectMastersList(const QString &camera) 0728 { 0729 if (darkFramesModel->rowCount() == 0) 0730 return; 0731 0732 masterDarksCombo->blockSignals(true); 0733 masterDarksCombo->clear(); 0734 0735 for (int i = 0; i < darkFramesModel->rowCount(); ++i) 0736 { 0737 QSqlRecord record = darkFramesModel->record(i); 0738 0739 if (record.value("ccd") != camera) 0740 continue; 0741 0742 auto binX = record.value("binX").toInt(); 0743 auto binY = record.value("binY").toInt(); 0744 auto temperature = record.value("temperature").toDouble(); 0745 auto duration = record.value("duration").toDouble(); 0746 auto gain = record.value("gain").toInt(); 0747 auto iso = record.value("iso").toString(); 0748 QString ts = record.value("timestamp").toString(); 0749 0750 QString entry = QString("%1 secs %2x%3") 0751 .arg(QString::number(duration, 'f', 1)) 0752 .arg(QString::number(binX)) 0753 .arg(QString::number(binY)); 0754 0755 if (temperature > INVALID_VALUE) 0756 entry.append(QString(" @ %1°").arg(QString::number(temperature, 'f', 1))); 0757 0758 if (gain >= 0) 0759 entry.append(QString(" G %1").arg(gain)); 0760 if (!iso.isEmpty()) 0761 entry.append(QString(" ISO %1").arg(iso)); 0762 0763 masterDarksCombo->addItem(entry); 0764 } 0765 0766 masterDarksCombo->blockSignals(false); 0767 0768 //loadDefectMap(); 0769 0770 } 0771 /////////////////////////////////////////////////////////////////////////////////////// 0772 /// 0773 /////////////////////////////////////////////////////////////////////////////////////// 0774 void DarkLibrary::reloadDarksFromDatabase() 0775 { 0776 auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName()); 0777 0778 const QString camera = m_Camera->getDeviceName(); 0779 0780 delete (darkFramesModel); 0781 delete (sortFilter); 0782 0783 darkFramesModel = new QSqlTableModel(this, userdb); 0784 darkFramesModel->setTable("darkframe"); 0785 darkFramesModel->setFilter(QString("ccd='%1'").arg(camera)); 0786 darkFramesModel->select(); 0787 0788 sortFilter = new QSortFilterProxyModel(this); 0789 sortFilter->setSourceModel(darkFramesModel); 0790 sortFilter->sort (0); 0791 darkTableView->setModel (sortFilter); 0792 0793 //darkTableView->setModel(darkFramesModel); 0794 // Hide ID 0795 darkTableView->hideColumn(0); 0796 // Hide Chip 0797 darkTableView->hideColumn(2); 0798 0799 if (darkFramesModel->rowCount() == 0 && m_CurrentDarkFrame) 0800 { 0801 clearBuffers(); 0802 return; 0803 } 0804 0805 refreshDefectMastersList(camera); 0806 loadCurrentMasterDark(camera); 0807 } 0808 0809 /////////////////////////////////////////////////////////////////////////////////////// 0810 /// 0811 /////////////////////////////////////////////////////////////////////////////////////// 0812 void DarkLibrary::loadCurrentMasterDark(const QString &camera, int masterIndex) 0813 { 0814 // Do not process empty models 0815 if (darkFramesModel->rowCount() == 0) 0816 return; 0817 0818 if (masterIndex == -1) 0819 masterIndex = masterDarksCombo->currentIndex(); 0820 0821 if (masterIndex < 0 || masterIndex >= darkFramesModel->rowCount()) 0822 return; 0823 0824 QSqlRecord record = darkFramesModel->record(masterIndex); 0825 if (record.value("ccd") != camera) 0826 return; 0827 // Get the master dark frame file name 0828 m_MasterDarkFrameFilename = record.value("filename").toString(); 0829 0830 if (m_MasterDarkFrameFilename.isEmpty() || !QFileInfo::exists(m_MasterDarkFrameFilename)) 0831 return; 0832 0833 // Get defect file name as well if available. 0834 m_DefectMapFilename = record.value("defectmap").toString(); 0835 0836 // If current dark frame is different from target filename, then load from file 0837 if (m_CurrentDarkFrame->filename() != m_MasterDarkFrameFilename) 0838 m_DarkFrameFutureWatcher.setFuture(m_CurrentDarkFrame->loadFromFile(m_MasterDarkFrameFilename)); 0839 // If current dark frame is the same one loaded, then check if we need to reload defect map 0840 else 0841 loadCurrentMasterDefectMap(); 0842 } 0843 0844 /////////////////////////////////////////////////////////////////////////////////////// 0845 /// 0846 /////////////////////////////////////////////////////////////////////////////////////// 0847 void DarkLibrary::loadCurrentMasterDefectMap() 0848 { 0849 // Find if we have an existing map 0850 if (m_CachedDefectMaps.contains(m_MasterDarkFrameFilename)) 0851 { 0852 if (m_CurrentDefectMap != m_CachedDefectMaps.value(m_MasterDarkFrameFilename)) 0853 { 0854 m_CurrentDefectMap = m_CachedDefectMaps.value(m_MasterDarkFrameFilename); 0855 m_DarkView->setDefectMap(m_CurrentDefectMap); 0856 m_CurrentDefectMap->setDarkData(m_CurrentDarkFrame); 0857 } 0858 } 0859 // Create new defect map 0860 else 0861 { 0862 m_CurrentDefectMap.reset(new DefectMap()); 0863 connect(m_CurrentDefectMap.data(), &DefectMap::pixelsUpdated, this, [this](uint32_t hot, uint32_t cold) 0864 { 0865 hotPixelsCount->setValue(hot); 0866 coldPixelsCount->setValue(cold); 0867 aggresivenessHotSlider->setValue(m_CurrentDefectMap->property("HotPixelAggressiveness").toInt()); 0868 aggresivenessColdSlider->setValue(m_CurrentDefectMap->property("ColdPixelAggressiveness").toInt()); 0869 }); 0870 0871 if (!m_DefectMapFilename.isEmpty()) 0872 cacheDefectMapFromFile(m_MasterDarkFrameFilename, m_DefectMapFilename); 0873 0874 m_DarkView->setDefectMap(m_CurrentDefectMap); 0875 m_CurrentDefectMap->setDarkData(m_CurrentDarkFrame); 0876 } 0877 } 0878 0879 /////////////////////////////////////////////////////////////////////////////////////// 0880 /// 0881 /////////////////////////////////////////////////////////////////////////////////////// 0882 void DarkLibrary::populateMasterMetedata() 0883 { 0884 if (m_CurrentDarkFrame.isNull()) 0885 return; 0886 0887 QVariant value; 0888 // TS 0889 if (m_CurrentDarkFrame->getRecordValue("DATE-OBS", value)) 0890 masterTime->setText(value.toString()); 0891 // Temperature 0892 if (m_CurrentDarkFrame->getRecordValue("CCD-TEMP", value) && value.toDouble() < 100) 0893 masterTemperature->setText(QString::number(value.toDouble(), 'f', 1)); 0894 // Exposure 0895 if (m_CurrentDarkFrame->getRecordValue("EXPTIME", value)) 0896 masterExposure->setText(value.toString()); 0897 // Median 0898 { 0899 double median = m_CurrentDarkFrame->getAverageMedian(); 0900 if (median > 0) 0901 masterMedian->setText(QString::number(median, 'f', 1)); 0902 } 0903 // Mean 0904 { 0905 double mean = m_CurrentDarkFrame->getAverageMean(); 0906 masterMean->setText(QString::number(mean, 'f', 1)); 0907 } 0908 // Standard Deviation 0909 { 0910 double stddev = m_CurrentDarkFrame->getAverageStdDev(); 0911 masterDeviation->setText(QString::number(stddev, 'f', 1)); 0912 } 0913 } 0914 0915 /////////////////////////////////////////////////////////////////////////////////////// 0916 /// 0917 /////////////////////////////////////////////////////////////////////////////////////// 0918 /////////////////////////////////////////////////////////////////////////////////////// 0919 /// 0920 /////////////////////////////////////////////////////////////////////////////////////// 0921 void DarkLibrary::loadIndexInView(int row) 0922 { 0923 QSqlRecord record = darkFramesModel->record(row); 0924 QString filename = record.value("filename").toString(); 0925 // Avoid duplicate loads 0926 if (m_DarkView->imageData().isNull() || m_DarkView->imageData()->filename() != filename) 0927 m_DarkView->loadFile(filename); 0928 } 0929 0930 /////////////////////////////////////////////////////////////////////////////////////// 0931 /// 0932 /////////////////////////////////////////////////////////////////////////////////////// 0933 bool DarkLibrary::setCamera(ISD::Camera * device) 0934 { 0935 if (m_Camera == device) 0936 return false; 0937 0938 if (m_Camera) 0939 m_Camera->disconnect(this); 0940 0941 m_Camera = device; 0942 0943 if (m_Camera) 0944 { 0945 darkTabsWidget->setEnabled(true); 0946 checkCamera(); 0947 reloadDarksFromDatabase(); 0948 return true; 0949 } 0950 else 0951 { 0952 darkTabsWidget->setEnabled(false); 0953 return false; 0954 } 0955 } 0956 0957 /////////////////////////////////////////////////////////////////////////////////////// 0958 /// 0959 /////////////////////////////////////////////////////////////////////////////////////// 0960 void DarkLibrary::removeDevice(const QSharedPointer<ISD::GenericDevice> &device) 0961 { 0962 if (m_Camera && m_Camera->getDeviceName() == device->getDeviceName()) 0963 { 0964 m_Camera->disconnect(this); 0965 m_Camera = nullptr; 0966 } 0967 } 0968 0969 /////////////////////////////////////////////////////////////////////////////////////// 0970 /// 0971 /////////////////////////////////////////////////////////////////////////////////////// 0972 void DarkLibrary::checkCamera() 0973 { 0974 if (!m_Camera) 0975 return; 0976 0977 auto device = m_Camera->getDeviceName(); 0978 0979 m_TargetChip = nullptr; 0980 // FIXME TODO 0981 // Need to figure guide head 0982 if (device.contains("Guider")) 0983 { 0984 m_UseGuideHead = true; 0985 m_TargetChip = m_Camera->getChip(ISD::CameraChip::GUIDE_CCD); 0986 } 0987 0988 if (m_TargetChip == nullptr) 0989 { 0990 m_UseGuideHead = false; 0991 m_TargetChip = m_Camera->getChip(ISD::CameraChip::PRIMARY_CCD); 0992 } 0993 0994 // Make sure we have a valid chip and valid base device. 0995 // Make sure we are not in capture process. 0996 if (!m_TargetChip || !m_TargetChip->getCCD() || m_TargetChip->isCapturing()) 0997 return; 0998 0999 if (m_Camera->hasCoolerControl()) 1000 { 1001 temperatureLabel->setEnabled(true); 1002 temperatureStepLabel->setEnabled(true); 1003 temperatureToLabel->setEnabled(true); 1004 temperatureStepSpin->setEnabled(true); 1005 minTemperatureSpin->setEnabled(true); 1006 maxTemperatureSpin->setEnabled(true); 1007 1008 // Get default temperature 1009 double temperature = 0; 1010 // Update if no setting was previously set 1011 if (m_Camera->getTemperature(&temperature)) 1012 { 1013 minTemperatureSpin->setValue(temperature); 1014 maxTemperatureSpin->setValue(temperature); 1015 } 1016 1017 } 1018 else 1019 { 1020 temperatureLabel->setEnabled(false); 1021 temperatureStepLabel->setEnabled(false); 1022 temperatureToLabel->setEnabled(false); 1023 temperatureStepSpin->setEnabled(false); 1024 minTemperatureSpin->setEnabled(false); 1025 maxTemperatureSpin->setEnabled(false); 1026 } 1027 1028 QStringList isoList = m_TargetChip->getISOList(); 1029 captureISOS->blockSignals(true); 1030 captureISOS->clear(); 1031 1032 // No ISO range available 1033 if (isoList.isEmpty()) 1034 { 1035 captureISOS->setEnabled(false); 1036 } 1037 else 1038 { 1039 captureISOS->setEnabled(true); 1040 captureISOS->addItems(isoList); 1041 captureISOS->setCurrentIndex(m_TargetChip->getISOIndex()); 1042 } 1043 captureISOS->blockSignals(false); 1044 1045 // Gain Check 1046 if (m_Camera->hasGain()) 1047 { 1048 double min, max, step, value, targetCustomGain; 1049 m_Camera->getGainMinMaxStep(&min, &max, &step); 1050 1051 // Allow the possibility of no gain value at all. 1052 GainSpinSpecialValue = min - step; 1053 captureGainN->setRange(GainSpinSpecialValue, max); 1054 captureGainN->setSpecialValueText(i18n("--")); 1055 captureGainN->setEnabled(true); 1056 captureGainN->setSingleStep(step); 1057 m_Camera->getGain(&value); 1058 1059 targetCustomGain = getGain(); 1060 1061 // Set the custom gain if we have one 1062 // otherwise it will not have an effect. 1063 if (targetCustomGain > 0) 1064 captureGainN->setValue(targetCustomGain); 1065 else 1066 captureGainN->setValue(GainSpinSpecialValue); 1067 1068 captureGainN->setReadOnly(m_Camera->getGainPermission() == IP_RO); 1069 } 1070 else 1071 captureGainN->setEnabled(false); 1072 1073 countDarkTotalTime(); 1074 1075 } 1076 1077 /////////////////////////////////////////////////////////////////////////////////////// 1078 /// 1079 /////////////////////////////////////////////////////////////////////////////////////// 1080 void DarkLibrary::countDarkTotalTime() 1081 { 1082 double temperatureCount = 1; 1083 if (m_Camera && m_Camera->hasCoolerControl() && std::abs(maxTemperatureSpin->value() - minTemperatureSpin->value()) > 0) 1084 temperatureCount = (std::abs((maxTemperatureSpin->value() - minTemperatureSpin->value())) / temperatureStepSpin->value()) + 1085 1; 1086 int binnings = 0; 1087 if (bin1Check->isChecked()) 1088 binnings++; 1089 if (bin2Check->isChecked()) 1090 binnings++; 1091 if (bin4Check->isChecked()) 1092 binnings++; 1093 1094 double darkTime = 0; 1095 int imagesCount = 0; 1096 for (double startExposure = minExposureSpin->value(); startExposure <= maxExposureSpin->value(); 1097 startExposure += exposureStepSin->value()) 1098 { 1099 darkTime += startExposure * temperatureCount * binnings * countSpin->value(); 1100 imagesCount += temperatureCount * binnings * countSpin->value(); 1101 } 1102 1103 totalTime->setText(QString::number(darkTime / 60.0, 'f', 1)); 1104 totalImages->setText(QString::number(imagesCount)); 1105 darkProgress->setMaximum(imagesCount); 1106 1107 } 1108 1109 /////////////////////////////////////////////////////////////////////////////////////// 1110 /// 1111 /////////////////////////////////////////////////////////////////////////////////////// 1112 void DarkLibrary::generateDarkJobs() 1113 { 1114 // Always clear sequence queue before starting 1115 m_CaptureModule->clearSequenceQueue(); 1116 1117 if (m_JobsGenerated == false) 1118 { 1119 m_JobsGenerated = true; 1120 m_PresetSettings = m_CaptureModule->getPresetSettings(); 1121 m_FileSettings = m_CaptureModule->getFileSettings(); 1122 } 1123 1124 QList<double> temperatures; 1125 if (m_Camera->hasCoolerControl() && std::fabs(maxTemperatureSpin->value() - minTemperatureSpin->value()) >= 0) 1126 { 1127 for (double oneTemperature = minTemperatureSpin->value(); oneTemperature <= maxTemperatureSpin->value(); 1128 oneTemperature += temperatureStepSpin->value()) 1129 { 1130 temperatures << oneTemperature; 1131 } 1132 1133 // Enforce temperature set 1134 m_CaptureModule->setForceTemperature(true); 1135 } 1136 else 1137 { 1138 // Disable temperature set 1139 m_CaptureModule->setForceTemperature(false); 1140 temperatures << INVALID_VALUE; 1141 } 1142 1143 QList<uint8_t> bins; 1144 if (bin1Check->isChecked()) 1145 bins << 1; 1146 if (bin2Check->isChecked()) 1147 bins << 2; 1148 if (bin4Check->isChecked()) 1149 bins << 4; 1150 1151 QList<double> exposures; 1152 for (double oneExposure = minExposureSpin->value(); oneExposure <= maxExposureSpin->value(); 1153 oneExposure += exposureStepSin->value()) 1154 { 1155 exposures << oneExposure; 1156 } 1157 1158 QString prefix = QDir::tempPath() + QDir::separator() + QString::number(QDateTime::currentSecsSinceEpoch()) + 1159 QDir::separator(); 1160 1161 1162 int sequence = 0; 1163 for (auto &oneTemperature : temperatures) 1164 { 1165 for (auto &oneExposure : exposures) 1166 { 1167 for (auto &oneBin : bins) 1168 { 1169 sequence++; 1170 1171 QJsonObject settings; 1172 1173 settings["optical_train"] = opticalTrainCombo->currentText(); 1174 settings["exp"] = oneExposure; 1175 settings["bin"] = oneBin; 1176 settings["frameType"] = FRAME_DARK; 1177 settings["temperature"] = oneTemperature; 1178 if (captureGainN->isEnabled()) 1179 settings["gain"] = captureGainN->value(); 1180 if (captureISOS->isEnabled()) 1181 settings["iso"] = captureISOS->currentIndex(); 1182 1183 QString directory = prefix + QString("sequence_%1").arg(sequence); 1184 QJsonObject fileSettings; 1185 1186 fileSettings["directory"] = directory; 1187 m_CaptureModule->setPresetSettings(settings); 1188 m_CaptureModule->setFileSettings(fileSettings); 1189 m_CaptureModule->setCount(countSpin->value()); 1190 m_CaptureModule->createJob(); 1191 } 1192 } 1193 } 1194 } 1195 1196 /////////////////////////////////////////////////////////////////////////////////////// 1197 /// 1198 /////////////////////////////////////////////////////////////////////////////////////// 1199 void DarkLibrary::execute() 1200 { 1201 m_DarkImagesCounter = 0; 1202 darkProgress->setValue(0); 1203 darkProgress->setTextVisible(true); 1204 connect(m_CaptureModule, &Capture::newImage, this, &DarkLibrary::processNewImage, Qt::UniqueConnection); 1205 connect(m_CaptureModule, &Capture::newStatus, this, &DarkLibrary::setCaptureState, Qt::UniqueConnection); 1206 connect(m_Camera, &ISD::Camera::propertyUpdated, this, &DarkLibrary::updateProperty, Qt::UniqueConnection); 1207 1208 Options::setUseFITSViewer(false); 1209 Options::setUseSummaryPreview(false); 1210 startB->setEnabled(false); 1211 stopB->setEnabled(true); 1212 m_DarkView->reset(); 1213 m_StatusLabel->setText(i18n("In progress...")); 1214 m_CaptureModule->start(); 1215 1216 } 1217 1218 /////////////////////////////////////////////////////////////////////////////////////// 1219 /// 1220 /////////////////////////////////////////////////////////////////////////////////////// 1221 void DarkLibrary::stop() 1222 { 1223 m_CaptureModule->abort(); 1224 darkProgress->setValue(0); 1225 m_DarkView->reset(); 1226 } 1227 1228 /////////////////////////////////////////////////////////////////////////////////////// 1229 /// 1230 /////////////////////////////////////////////////////////////////////////////////////// 1231 void DarkLibrary::initView() 1232 { 1233 m_DarkView.reset(new DarkView(darkWidget)); 1234 m_DarkView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 1235 m_DarkView->setBaseSize(darkWidget->size()); 1236 m_DarkView->createFloatingToolBar(); 1237 QVBoxLayout *vlayout = new QVBoxLayout(); 1238 vlayout->addWidget(m_DarkView.get()); 1239 darkWidget->setLayout(vlayout); 1240 connect(m_DarkView.get(), &DarkView::loaded, this, [this]() 1241 { 1242 emit newImage(m_DarkView->imageData()); 1243 }); 1244 } 1245 1246 /////////////////////////////////////////////////////////////////////////////////////// 1247 /// 1248 /////////////////////////////////////////////////////////////////////////////////////// 1249 void DarkLibrary::aggregate(const QSharedPointer<FITSData> &data) 1250 { 1251 switch (data->dataType()) 1252 { 1253 case TBYTE: 1254 aggregateInternal<uint8_t>(data); 1255 break; 1256 1257 case TSHORT: 1258 aggregateInternal<int16_t>(data); 1259 break; 1260 1261 case TUSHORT: 1262 aggregateInternal<uint16_t>(data); 1263 break; 1264 1265 case TLONG: 1266 aggregateInternal<int32_t>(data); 1267 break; 1268 1269 case TULONG: 1270 aggregateInternal<uint32_t>(data); 1271 break; 1272 1273 case TFLOAT: 1274 aggregateInternal<float>(data); 1275 break; 1276 1277 case TLONGLONG: 1278 aggregateInternal<int64_t>(data); 1279 break; 1280 1281 case TDOUBLE: 1282 aggregateInternal<double>(data); 1283 break; 1284 1285 default: 1286 break; 1287 } 1288 } 1289 1290 /////////////////////////////////////////////////////////////////////////////////////// 1291 /// 1292 /////////////////////////////////////////////////////////////////////////////////////// 1293 template <typename T> 1294 void DarkLibrary::aggregateInternal(const QSharedPointer<FITSData> &data) 1295 { 1296 T const *darkBuffer = reinterpret_cast<T const*>(data->getImageBuffer()); 1297 for (uint32_t i = 0; i < m_DarkMasterBuffer.size(); i++) 1298 m_DarkMasterBuffer[i] += darkBuffer[i]; 1299 } 1300 1301 /////////////////////////////////////////////////////////////////////////////////////// 1302 /// 1303 /////////////////////////////////////////////////////////////////////////////////////// 1304 void DarkLibrary::generateMasterFrame(const QSharedPointer<FITSData> &data, const QJsonObject &metadata) 1305 { 1306 switch (data->dataType()) 1307 { 1308 case TBYTE: 1309 generateMasterFrameInternal<uint8_t>(data, metadata); 1310 break; 1311 1312 case TSHORT: 1313 generateMasterFrameInternal<int16_t>(data, metadata); 1314 break; 1315 1316 case TUSHORT: 1317 generateMasterFrameInternal<uint16_t>(data, metadata); 1318 break; 1319 1320 case TLONG: 1321 generateMasterFrameInternal<int32_t>(data, metadata); 1322 break; 1323 1324 case TULONG: 1325 generateMasterFrameInternal<uint32_t>(data, metadata); 1326 break; 1327 1328 case TFLOAT: 1329 generateMasterFrameInternal<float>(data, metadata); 1330 break; 1331 1332 case TLONGLONG: 1333 generateMasterFrameInternal<int64_t>(data, metadata); 1334 break; 1335 1336 case TDOUBLE: 1337 generateMasterFrameInternal<double>(data, metadata); 1338 break; 1339 1340 default: 1341 break; 1342 } 1343 1344 emit newImage(data); 1345 // Reset Master Buffer 1346 m_DarkMasterBuffer.assign(m_DarkMasterBuffer.size(), 0); 1347 1348 } 1349 1350 /////////////////////////////////////////////////////////////////////////////////////// 1351 /// 1352 /////////////////////////////////////////////////////////////////////////////////////// 1353 template <typename T> void DarkLibrary::generateMasterFrameInternal(const QSharedPointer<FITSData> &data, 1354 const QJsonObject &metadata) 1355 { 1356 T *writableBuffer = reinterpret_cast<T *>(data->getWritableImageBuffer()); 1357 const uint32_t count = metadata["count"].toInt(); 1358 // Average the values 1359 for (uint32_t i = 0; i < m_DarkMasterBuffer.size(); i++) 1360 writableBuffer[i] = m_DarkMasterBuffer[i] / count; 1361 1362 QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss"); 1363 QString path = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("darks/darkframe_" + ts + 1364 data->extension()); 1365 1366 data->calculateStats(true); 1367 if (!data->saveImage(path)) 1368 { 1369 m_FileLabel->setText(i18n("Failed to save master frame: %1", data->getLastError())); 1370 return; 1371 } 1372 1373 auto memoryMB = KSUtils::getAvailableRAM() / 1e6; 1374 if (memoryMB > CACHE_MEMORY_LIMIT) 1375 cacheDarkFrameFromFile(data->filename()); 1376 1377 QVariantMap map; 1378 map["ccd"] = metadata["camera"].toString(); 1379 map["chip"] = metadata["chip"].toInt(); 1380 map["binX"] = metadata["binx"].toInt(); 1381 map["binY"] = metadata["biny"].toInt(); 1382 map["temperature"] = metadata["temperature"].toDouble(INVALID_VALUE); 1383 map["gain"] = metadata["gain"].toInt(-1); 1384 map["iso"] = metadata["iso"].toString(); 1385 map["duration"] = metadata["duration"].toDouble(); 1386 map["filename"] = path; 1387 map["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate); 1388 1389 m_DarkFramesDatabaseList.append(map); 1390 m_FileLabel->setText(i18n("Master Dark saved to %1", path)); 1391 KStarsData::Instance()->userdb()->AddDarkFrame(map); 1392 } 1393 1394 /////////////////////////////////////////////////////////////////////////////////////// 1395 /// 1396 /////////////////////////////////////////////////////////////////////////////////////// 1397 void DarkLibrary::setCaptureModule(Capture *instance) 1398 { 1399 m_CaptureModule = instance; 1400 } 1401 1402 /////////////////////////////////////////////////////////////////////////////////////// 1403 /// 1404 /////////////////////////////////////////////////////////////////////////////////////// 1405 void DarkLibrary::setCaptureState(CaptureState state) 1406 { 1407 switch (state) 1408 { 1409 case CAPTURE_ABORTED: 1410 setCompleted(); 1411 m_StatusLabel->setText(i18n("Capture aborted.")); 1412 break; 1413 case CAPTURE_COMPLETE: 1414 setCompleted(); 1415 m_StatusLabel->setText(i18n("Capture completed.")); 1416 break; 1417 default: 1418 break; 1419 } 1420 } 1421 1422 /////////////////////////////////////////////////////////////////////////////////////// 1423 /// 1424 /////////////////////////////////////////////////////////////////////////////////////// 1425 void DarkLibrary::saveDefectMap() 1426 { 1427 if (!m_CurrentDarkFrame) 1428 return; 1429 1430 QString filename = m_CurrentDefectMap->filename(); 1431 bool newFile = false; 1432 if (filename.isEmpty()) 1433 { 1434 QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss"); 1435 filename = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("defectmaps/defectmap_" + ts + 1436 ".json"); 1437 newFile = true; 1438 } 1439 1440 if (m_CurrentDefectMap->save(filename, m_Camera->getDeviceName())) 1441 { 1442 m_FileLabel->setText(i18n("Defect map saved to %1", filename)); 1443 1444 if (newFile) 1445 { 1446 auto currentMap = std::find_if(m_DarkFramesDatabaseList.begin(), 1447 m_DarkFramesDatabaseList.end(), [&](const QVariantMap & oneMap) 1448 { 1449 return oneMap["filename"].toString() == m_CurrentDarkFrame->filename(); 1450 }); 1451 1452 if (currentMap != m_DarkFramesDatabaseList.end()) 1453 { 1454 (*currentMap)["defectmap"] = filename; 1455 (*currentMap)["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate); 1456 KStarsData::Instance()->userdb()->UpdateDarkFrame(*currentMap); 1457 } 1458 } 1459 } 1460 else 1461 { 1462 m_FileLabel->setText(i18n("Failed to save defect map to %1", filename)); 1463 } 1464 } 1465 1466 /////////////////////////////////////////////////////////////////////////////////////// 1467 /// 1468 /////////////////////////////////////////////////////////////////////////////////////// 1469 void DarkLibrary::start() 1470 { 1471 generateDarkJobs(); 1472 execute(); 1473 } 1474 1475 /////////////////////////////////////////////////////////////////////////////////////// 1476 /// 1477 /////////////////////////////////////////////////////////////////////////////////////// 1478 void DarkLibrary::setCameraPresets(const QJsonObject &settings) 1479 { 1480 const auto opticalTrain = settings["optical_train"].toString(); 1481 const auto isDarkPrefer = settings["isDarkPrefer"].toBool(preferDarksRadio->isChecked()); 1482 const auto isDefectPrefer = settings["isDefectPrefer"].toBool(preferDefectsRadio->isChecked()); 1483 opticalTrainCombo->setCurrentText(opticalTrain); 1484 preferDarksRadio->setChecked(isDarkPrefer); 1485 preferDefectsRadio->setChecked(isDefectPrefer); 1486 checkCamera(); 1487 reloadDarksFromDatabase(); 1488 } 1489 1490 /////////////////////////////////////////////////////////////////////////////////////// 1491 /// 1492 /////////////////////////////////////////////////////////////////////////////////////// 1493 QJsonObject DarkLibrary::getCameraPresets() 1494 { 1495 QJsonObject cameraSettings = 1496 { 1497 {"optical_train", opticalTrainCombo->currentText()}, 1498 {"preferDarksRadio", preferDarksRadio->isChecked()}, 1499 {"preferDefectsRadio", preferDefectsRadio->isChecked()}, 1500 {"fileName", m_FileLabel->text()} 1501 }; 1502 return cameraSettings; 1503 } 1504 1505 /////////////////////////////////////////////////////////////////////////////////////// 1506 /// 1507 /////////////////////////////////////////////////////////////////////////////////////// 1508 QJsonArray DarkLibrary::getViewMasters() 1509 { 1510 QJsonArray array; 1511 1512 for(int i = 0; i < darkFramesModel->rowCount(); i++) 1513 { 1514 QSqlRecord record = darkFramesModel->record(i); 1515 auto camera = record.value("ccd").toString(); 1516 auto binX = record.value("binX").toInt(); 1517 auto binY = record.value("binY").toInt(); 1518 auto temperature = record.value("temperature").toDouble(); 1519 auto duration = record.value("duration").toDouble(); 1520 auto ts = record.value("timestamp").toString(); 1521 auto gain = record.value("gain").toInt(); 1522 auto iso = record.value("iso").toString(); 1523 1524 QJsonObject filterRows = 1525 { 1526 {"camera", camera}, 1527 {"binX", binX}, 1528 {"binY", binY}, 1529 {"temperature", temperature}, 1530 {"duaration", duration}, 1531 {"ts", ts} 1532 }; 1533 1534 if (gain >= 0) 1535 filterRows["gain"] = gain; 1536 if (!iso.isEmpty()) 1537 filterRows["iso"] = iso; 1538 1539 array.append(filterRows); 1540 } 1541 return array; 1542 } 1543 1544 /////////////////////////////////////////////////////////////////////////////////////// 1545 /// 1546 /////////////////////////////////////////////////////////////////////////////////////// 1547 void DarkLibrary::setDefectPixels(const QJsonObject &payload) 1548 { 1549 const auto hotSpin = payload["hotSpin"].toInt(); 1550 const auto coldSpin = payload["coldSpin"].toInt(); 1551 const auto hotEnabled = payload["hotEnabled"].toBool(hotPixelsEnabled->isChecked()); 1552 const auto coldEnabled = payload["coldEnabled"].toBool(coldPixelsEnabled->isChecked()); 1553 1554 hotPixelsEnabled->setChecked(hotEnabled); 1555 coldPixelsEnabled->setChecked(coldEnabled); 1556 1557 aggresivenessHotSpin->setValue(hotSpin); 1558 aggresivenessColdSpin->setValue(coldSpin); 1559 1560 m_DarkView->ZoomDefault(); 1561 1562 setDefectMapEnabled(true); 1563 generateMapB->click(); 1564 } 1565 1566 /////////////////////////////////////////////////////////////////////////////////////// 1567 /// 1568 /////////////////////////////////////////////////////////////////////////////////////// 1569 void DarkLibrary::setDefectMapEnabled(bool enabled) 1570 { 1571 m_DarkView->setDefectMapEnabled(enabled); 1572 } 1573 1574 /////////////////////////////////////////////////////////////////////////////////////// 1575 /// 1576 /////////////////////////////////////////////////////////////////////////////////////// 1577 double DarkLibrary::getGain() 1578 { 1579 // Gain is manifested in two forms 1580 // Property CCD_GAIN and 1581 // Part of CCD_CONTROLS properties. 1582 // Therefore, we have to find what the currently camera supports first. 1583 auto gain = m_Camera->getProperty("CCD_GAIN"); 1584 if (gain) 1585 return gain.getNumber()->at(0)->value; 1586 1587 1588 auto controls = m_Camera->getProperty("CCD_CONTROLS"); 1589 if (controls) 1590 { 1591 auto oneGain = controls.getNumber()->findWidgetByName("Gain"); 1592 if (oneGain) 1593 return oneGain->value; 1594 } 1595 1596 return -1; 1597 } 1598 1599 /////////////////////////////////////////////////////////////////////////////////////// 1600 /// 1601 /////////////////////////////////////////////////////////////////////////////////////// 1602 void DarkLibrary::setupOpticalTrainManager() 1603 { 1604 connect(OpticalTrainManager::Instance(), &OpticalTrainManager::updated, this, &DarkLibrary::refreshOpticalTrain); 1605 connect(trainB, &QPushButton::clicked, this, [this]() 1606 { 1607 OpticalTrainManager::Instance()->openEditor(opticalTrainCombo->currentText()); 1608 }); 1609 connect(opticalTrainCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int index) 1610 { 1611 ProfileSettings::Instance()->setOneSetting(ProfileSettings::DarkLibraryOpticalTrain, 1612 OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(index))); 1613 refreshOpticalTrain(); 1614 emit trainChanged(); 1615 }); 1616 } 1617 1618 /////////////////////////////////////////////////////////////////////////////////////// 1619 /// 1620 /////////////////////////////////////////////////////////////////////////////////////// 1621 void DarkLibrary::refreshOpticalTrain() 1622 { 1623 opticalTrainCombo->blockSignals(true); 1624 opticalTrainCombo->clear(); 1625 opticalTrainCombo->addItems(OpticalTrainManager::Instance()->getTrainNames()); 1626 trainB->setEnabled(true); 1627 1628 QVariant trainID = ProfileSettings::Instance()->getOneSetting(ProfileSettings::DarkLibraryOpticalTrain); 1629 1630 if (trainID.isValid()) 1631 { 1632 auto id = trainID.toUInt(); 1633 1634 // If train not found, select the first one available. 1635 if (OpticalTrainManager::Instance()->exists(id) == false) 1636 { 1637 emit newLog(i18n("Optical train doesn't exist for id %1", id)); 1638 id = OpticalTrainManager::Instance()->id(opticalTrainCombo->itemText(0)); 1639 } 1640 1641 auto name = OpticalTrainManager::Instance()->name(id); 1642 1643 opticalTrainCombo->setCurrentText(name); 1644 1645 auto camera = OpticalTrainManager::Instance()->getCamera(name); 1646 if (camera) 1647 { 1648 auto scope = OpticalTrainManager::Instance()->getScope(name); 1649 opticalTrainCombo->setToolTip(QString("%1 @ %2").arg(camera->getDeviceName(), scope["name"].toString())); 1650 } 1651 setCamera(camera); 1652 1653 // Load train settings 1654 OpticalTrainSettings::Instance()->setOpticalTrainID(id); 1655 auto settings = OpticalTrainSettings::Instance()->getOneSetting(OpticalTrainSettings::DarkLibrary); 1656 if (settings.isValid()) 1657 setAllSettings(settings.toJsonObject().toVariantMap()); 1658 else 1659 m_Settings = m_GlobalSettings; 1660 } 1661 1662 opticalTrainCombo->blockSignals(false); 1663 } 1664 1665 /////////////////////////////////////////////////////////////////////////////////////// 1666 /// 1667 /////////////////////////////////////////////////////////////////////////////////////// 1668 void DarkLibrary::loadGlobalSettings() 1669 { 1670 QString key; 1671 QVariant value; 1672 1673 QVariantMap settings; 1674 // All Combo Boxes 1675 for (auto &oneWidget : findChildren<QComboBox*>()) 1676 { 1677 if (oneWidget->objectName() == "opticalTrainCombo") 1678 continue; 1679 1680 key = oneWidget->objectName(); 1681 value = Options::self()->property(key.toLatin1()); 1682 if (value.isValid()) 1683 { 1684 oneWidget->setCurrentText(value.toString()); 1685 settings[key] = value; 1686 } 1687 } 1688 1689 // All Double Spin Boxes 1690 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 1691 { 1692 key = oneWidget->objectName(); 1693 value = Options::self()->property(key.toLatin1()); 1694 if (value.isValid()) 1695 { 1696 oneWidget->setValue(value.toDouble()); 1697 settings[key] = value; 1698 } 1699 } 1700 1701 // All Spin Boxes 1702 for (auto &oneWidget : findChildren<QSpinBox*>()) 1703 { 1704 key = oneWidget->objectName(); 1705 value = Options::self()->property(key.toLatin1()); 1706 if (value.isValid()) 1707 { 1708 oneWidget->setValue(value.toInt()); 1709 settings[key] = value; 1710 } 1711 } 1712 1713 // All Checkboxes 1714 for (auto &oneWidget : findChildren<QCheckBox*>()) 1715 { 1716 key = oneWidget->objectName(); 1717 value = Options::self()->property(key.toLatin1()); 1718 if (value.isValid()) 1719 { 1720 oneWidget->setChecked(value.toBool()); 1721 settings[key] = value; 1722 } 1723 } 1724 1725 m_GlobalSettings = m_Settings = settings; 1726 } 1727 1728 1729 /////////////////////////////////////////////////////////////////////////////////////// 1730 /// 1731 /////////////////////////////////////////////////////////////////////////////////////// 1732 void DarkLibrary::connectSettings() 1733 { 1734 // All Combo Boxes 1735 for (auto &oneWidget : findChildren<QComboBox*>()) 1736 connect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::DarkLibrary::syncSettings); 1737 1738 // All Double Spin Boxes 1739 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 1740 connect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings); 1741 1742 // All Spin Boxes 1743 for (auto &oneWidget : findChildren<QSpinBox*>()) 1744 connect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings); 1745 1746 // All Checkboxes 1747 for (auto &oneWidget : findChildren<QCheckBox*>()) 1748 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings); 1749 1750 // All Radio buttons 1751 for (auto &oneWidget : findChildren<QRadioButton*>()) 1752 connect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings); 1753 1754 // Train combo box should NOT be synced. 1755 disconnect(opticalTrainCombo, QOverload<int>::of(&QComboBox::activated), this, &Ekos::DarkLibrary::syncSettings); 1756 } 1757 1758 /////////////////////////////////////////////////////////////////////////////////////// 1759 /// 1760 /////////////////////////////////////////////////////////////////////////////////////// 1761 void DarkLibrary::disconnectSettings() 1762 { 1763 // All Combo Boxes 1764 for (auto &oneWidget : findChildren<QComboBox*>()) 1765 disconnect(oneWidget, QOverload<int>::of(&QComboBox::activated), this, &Ekos::DarkLibrary::syncSettings); 1766 1767 // All Double Spin Boxes 1768 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 1769 disconnect(oneWidget, &QDoubleSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings); 1770 1771 // All Spin Boxes 1772 for (auto &oneWidget : findChildren<QSpinBox*>()) 1773 disconnect(oneWidget, &QSpinBox::editingFinished, this, &Ekos::DarkLibrary::syncSettings); 1774 1775 // All Checkboxes 1776 for (auto &oneWidget : findChildren<QCheckBox*>()) 1777 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings); 1778 1779 // All Radio buttons 1780 for (auto &oneWidget : findChildren<QRadioButton*>()) 1781 disconnect(oneWidget, &QCheckBox::toggled, this, &Ekos::DarkLibrary::syncSettings); 1782 1783 } 1784 1785 /////////////////////////////////////////////////////////////////////////////////////////// 1786 /// 1787 /////////////////////////////////////////////////////////////////////////////////////////// 1788 QVariantMap DarkLibrary::getAllSettings() const 1789 { 1790 QVariantMap settings; 1791 1792 // All Combo Boxes 1793 for (auto &oneWidget : findChildren<QComboBox*>()) 1794 settings.insert(oneWidget->objectName(), oneWidget->currentText()); 1795 1796 // All Double Spin Boxes 1797 for (auto &oneWidget : findChildren<QDoubleSpinBox*>()) 1798 settings.insert(oneWidget->objectName(), oneWidget->value()); 1799 1800 // All Spin Boxes 1801 for (auto &oneWidget : findChildren<QSpinBox*>()) 1802 settings.insert(oneWidget->objectName(), oneWidget->value()); 1803 1804 // All Checkboxes 1805 for (auto &oneWidget : findChildren<QCheckBox*>()) 1806 settings.insert(oneWidget->objectName(), oneWidget->isChecked()); 1807 1808 return settings; 1809 } 1810 1811 /////////////////////////////////////////////////////////////////////////////////////////// 1812 /// 1813 /////////////////////////////////////////////////////////////////////////////////////////// 1814 void DarkLibrary::setAllSettings(const QVariantMap &settings) 1815 { 1816 // Disconnect settings that we don't end up calling syncSettings while 1817 // performing the changes. 1818 disconnectSettings(); 1819 1820 for (auto &name : settings.keys()) 1821 { 1822 // Combo 1823 auto comboBox = findChild<QComboBox*>(name); 1824 if (comboBox) 1825 { 1826 syncControl(settings, name, comboBox); 1827 continue; 1828 } 1829 1830 // Double spinbox 1831 auto doubleSpinBox = findChild<QDoubleSpinBox*>(name); 1832 if (doubleSpinBox) 1833 { 1834 syncControl(settings, name, doubleSpinBox); 1835 continue; 1836 } 1837 1838 // spinbox 1839 auto spinBox = findChild<QSpinBox*>(name); 1840 if (spinBox) 1841 { 1842 syncControl(settings, name, spinBox); 1843 continue; 1844 } 1845 1846 // checkbox 1847 auto checkbox = findChild<QCheckBox*>(name); 1848 if (checkbox) 1849 { 1850 syncControl(settings, name, checkbox); 1851 continue; 1852 } 1853 1854 // Radio button 1855 auto radioButton = findChild<QRadioButton*>(name); 1856 if (radioButton) 1857 { 1858 syncControl(settings, name, radioButton); 1859 continue; 1860 } 1861 } 1862 1863 // Sync to options 1864 for (auto &key : settings.keys()) 1865 { 1866 auto value = settings[key]; 1867 // Save immediately 1868 Options::self()->setProperty(key.toLatin1(), value); 1869 1870 m_Settings[key] = value; 1871 m_GlobalSettings[key] = value; 1872 } 1873 1874 emit settingsUpdated(getAllSettings()); 1875 1876 // Save to optical train specific settings as well 1877 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText())); 1878 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::DarkLibrary, m_Settings); 1879 1880 // Restablish connections 1881 connectSettings(); 1882 } 1883 1884 /////////////////////////////////////////////////////////////////////////////////////////// 1885 /// 1886 /////////////////////////////////////////////////////////////////////////////////////////// 1887 bool DarkLibrary::syncControl(const QVariantMap &settings, const QString &key, QWidget * widget) 1888 { 1889 QSpinBox *pSB = nullptr; 1890 QDoubleSpinBox *pDSB = nullptr; 1891 QCheckBox *pCB = nullptr; 1892 QComboBox *pComboBox = nullptr; 1893 QRadioButton *pRadioButton = nullptr; 1894 bool ok = false; 1895 1896 if ((pSB = qobject_cast<QSpinBox *>(widget))) 1897 { 1898 const int value = settings[key].toInt(&ok); 1899 if (ok) 1900 { 1901 pSB->setValue(value); 1902 return true; 1903 } 1904 } 1905 else if ((pDSB = qobject_cast<QDoubleSpinBox *>(widget))) 1906 { 1907 const double value = settings[key].toDouble(&ok); 1908 if (ok) 1909 { 1910 pDSB->setValue(value); 1911 return true; 1912 } 1913 } 1914 else if ((pCB = qobject_cast<QCheckBox *>(widget))) 1915 { 1916 const bool value = settings[key].toBool(); 1917 pCB->setChecked(value); 1918 return true; 1919 } 1920 // ONLY FOR STRINGS, not INDEX 1921 else if ((pComboBox = qobject_cast<QComboBox *>(widget))) 1922 { 1923 const QString value = settings[key].toString(); 1924 pComboBox->setCurrentText(value); 1925 return true; 1926 } 1927 else if ((pRadioButton = qobject_cast<QRadioButton *>(widget))) 1928 { 1929 const bool value = settings[key].toBool(); 1930 pRadioButton->setChecked(value); 1931 return true; 1932 } 1933 1934 return false; 1935 }; 1936 1937 /////////////////////////////////////////////////////////////////////////////////////// 1938 /// 1939 /////////////////////////////////////////////////////////////////////////////////////// 1940 void DarkLibrary::syncSettings() 1941 { 1942 QDoubleSpinBox *dsb = nullptr; 1943 QSpinBox *sb = nullptr; 1944 QCheckBox *cb = nullptr; 1945 QComboBox *cbox = nullptr; 1946 QRadioButton *cradio = nullptr; 1947 1948 QString key; 1949 QVariant value; 1950 1951 if ( (dsb = qobject_cast<QDoubleSpinBox*>(sender()))) 1952 { 1953 key = dsb->objectName(); 1954 value = dsb->value(); 1955 1956 } 1957 else if ( (sb = qobject_cast<QSpinBox*>(sender()))) 1958 { 1959 key = sb->objectName(); 1960 value = sb->value(); 1961 } 1962 else if ( (cb = qobject_cast<QCheckBox*>(sender()))) 1963 { 1964 key = cb->objectName(); 1965 value = cb->isChecked(); 1966 } 1967 else if ( (cbox = qobject_cast<QComboBox*>(sender()))) 1968 { 1969 key = cbox->objectName(); 1970 value = cbox->currentText(); 1971 } 1972 else if ( (cradio = qobject_cast<QRadioButton*>(sender()))) 1973 { 1974 key = cradio->objectName(); 1975 1976 // N.B. Only store CHECKED radios, do not store unchecked ones 1977 // as we only have exclusive groups. 1978 if (cradio->isChecked() == false) 1979 { 1980 // Remove from setting if it was added before 1981 if (m_Settings.contains(key)) 1982 { 1983 m_Settings.remove(key); 1984 emit settingsUpdated(getAllSettings()); 1985 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText())); 1986 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::DarkLibrary, m_Settings); 1987 } 1988 return; 1989 } 1990 1991 value = true; 1992 } 1993 1994 // Save immediately 1995 Options::self()->setProperty(key.toLatin1(), value); 1996 Options::self()->save(); 1997 1998 m_Settings[key] = value; 1999 m_GlobalSettings[key] = value; 2000 2001 emit settingsUpdated(getAllSettings()); 2002 2003 // Save to optical train specific settings as well 2004 OpticalTrainSettings::Instance()->setOpticalTrainID(OpticalTrainManager::Instance()->id(opticalTrainCombo->currentText())); 2005 OpticalTrainSettings::Instance()->setOneSetting(OpticalTrainSettings::DarkLibrary, m_Settings); 2006 } 2007 2008 /////////////////////////////////////////////////////////////////////////////////////// 2009 /// 2010 /////////////////////////////////////////////////////////////////////////////////////// 2011 QJsonObject DarkLibrary::getDefectSettings() 2012 { 2013 QStringList darkMasters; 2014 for (int i = 0; i < masterDarksCombo->count(); i++) 2015 darkMasters << masterDarksCombo->itemText(i); 2016 2017 QJsonObject createDefectMaps = 2018 { 2019 {"masterTime", masterTime->text()}, 2020 {"masterDarks", darkMasters.join('|')}, 2021 {"masterExposure", masterExposure->text()}, 2022 {"masterTempreture", masterTemperature->text()}, 2023 {"masterMean", masterMean->text()}, 2024 {"masterMedian", masterMedian->text()}, 2025 {"masterDeviation", masterDeviation->text()}, 2026 {"hotPixelsEnabled", hotPixelsEnabled->isChecked()}, 2027 {"coldPixelsEnabled", coldPixelsEnabled->isChecked()}, 2028 }; 2029 return createDefectMaps; 2030 } 2031 2032 2033 2034 } 2035