File indexing completed on 2024-04-14 14:10:38
0001 /* 0002 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "fitstab.h" 0008 0009 #include "auxiliary/kspaths.h" 0010 #include "fitsdata.h" 0011 #include "fitshistogrameditor.h" 0012 #include "fitshistogramcommand.h" 0013 #include "fitsview.h" 0014 #include "fitsviewer.h" 0015 #include "ksnotification.h" 0016 #include "kstars.h" 0017 #include "Options.h" 0018 #include "ui_fitsheaderdialog.h" 0019 #include "ui_statform.h" 0020 #include "fitsstretchui.h" 0021 #include "skymap.h" 0022 #include <KMessageBox> 0023 #include <QtConcurrent> 0024 #include <QIcon> 0025 #include "ekos/auxiliary/stellarsolverprofile.h" 0026 0027 #include <fits_debug.h> 0028 0029 FITSTab::FITSTab(FITSViewer *parent) : QWidget(parent) 0030 { 0031 viewer = parent; 0032 undoStack = new QUndoStack(this); 0033 undoStack->setUndoLimit(10); 0034 undoStack->clear(); 0035 connect(undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(modifyFITSState(bool))); 0036 0037 m_PlateSolveWidget = new QDialog(this); 0038 statWidget = new QDialog(this); 0039 fitsHeaderDialog = new QDialog(this); 0040 m_HistogramEditor = new FITSHistogramEditor(this); 0041 connect(m_HistogramEditor, &FITSHistogramEditor::newHistogramCommand, this, [this](FITSHistogramCommand * command) 0042 { 0043 undoStack->push(command); 0044 }); 0045 } 0046 0047 FITSTab::~FITSTab() 0048 { 0049 } 0050 0051 void FITSTab::saveUnsaved() 0052 { 0053 if (undoStack->isClean() || m_View->getMode() != FITS_NORMAL) 0054 return; 0055 0056 QString caption = i18n("Save Changes to FITS?"); 0057 QString message = i18n("The current FITS file has unsaved changes. Would you like to save before closing it?"); 0058 0059 int ans = KMessageBox::warningYesNoCancel(nullptr, message, caption, KStandardGuiItem::save(), KStandardGuiItem::discard()); 0060 if (ans == KMessageBox::Yes) 0061 saveFile(); 0062 if (ans == KMessageBox::No) 0063 { 0064 undoStack->clear(); 0065 modifyFITSState(); 0066 } 0067 } 0068 0069 void FITSTab::closeEvent(QCloseEvent *ev) 0070 { 0071 saveUnsaved(); 0072 0073 if (undoStack->isClean()) 0074 ev->accept(); 0075 else 0076 ev->ignore(); 0077 } 0078 QString FITSTab::getPreviewText() const 0079 { 0080 return previewText; 0081 } 0082 0083 void FITSTab::setPreviewText(const QString &value) 0084 { 0085 previewText = value; 0086 } 0087 0088 void FITSTab::selectRecentFITS(int i) 0089 { 0090 loadFile(QUrl::fromLocalFile(recentImages->item(i)->text())); 0091 } 0092 0093 void FITSTab::clearRecentFITS() 0094 { 0095 disconnect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); 0096 recentImages->clear(); 0097 connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); 0098 } 0099 0100 bool FITSTab::setupView(FITSMode mode, FITSScale filter) 0101 { 0102 if (m_View.isNull()) 0103 { 0104 m_View.reset(new FITSView(this, mode, filter)); 0105 m_View->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0106 QVBoxLayout *vlayout = new QVBoxLayout(); 0107 0108 connect(m_View.get(), &FITSView::rectangleUpdated, this, [this](QRect roi) 0109 { 0110 displayStats(roi.isValid()); 0111 }); 0112 fitsSplitter = new QSplitter(Qt::Horizontal, this); 0113 fitsTools = new QToolBox(); 0114 0115 stat.setupUi(statWidget); 0116 m_PlateSolveUI.setupUi(m_PlateSolveWidget); 0117 0118 connect(m_PlateSolveUI.SolveButton, &QPushButton::clicked, this, &FITSTab::extractImage); 0119 0120 for (int i = 0; i <= STAT_STDDEV; i++) 0121 { 0122 for (int j = 0; j < 3; j++) 0123 { 0124 stat.statsTable->setItem(i, j, new QTableWidgetItem()); 0125 stat.statsTable->item(i, j)->setTextAlignment(Qt::AlignHCenter); 0126 } 0127 0128 // Set col span for items up to HFR 0129 if (i <= STAT_HFR) 0130 stat.statsTable->setSpan(i, 0, 1, 3); 0131 } 0132 0133 fitsTools->addItem(statWidget, i18n("Statistics")); 0134 0135 fitsTools->addItem(m_PlateSolveWidget, i18n("Plate Solving")); 0136 initSolverUI(); 0137 0138 fitsTools->addItem(m_HistogramEditor, i18n("Histogram")); 0139 0140 header.setupUi(fitsHeaderDialog); 0141 fitsTools->addItem(fitsHeaderDialog, i18n("FITS Header")); 0142 0143 QVBoxLayout *recentPanelLayout = new QVBoxLayout(); 0144 QWidget *recentPanel = new QWidget(fitsSplitter); 0145 recentPanel->setLayout(recentPanelLayout); 0146 fitsTools->addItem(recentPanel, i18n("Recent Images")); 0147 recentImages = new QListWidget(recentPanel); 0148 recentPanelLayout->addWidget(recentImages); 0149 QPushButton *clearRecent = new QPushButton(i18n("Clear")); 0150 recentPanelLayout->addWidget(clearRecent); 0151 connect(clearRecent, &QPushButton::pressed, this, &FITSTab::clearRecentFITS); 0152 connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); 0153 0154 QScrollArea *scrollFitsPanel = new QScrollArea(fitsSplitter); 0155 scrollFitsPanel->setWidgetResizable(true); 0156 scrollFitsPanel->setWidget(fitsTools); 0157 0158 fitsSplitter->addWidget(scrollFitsPanel); 0159 fitsSplitter->addWidget(m_View.get()); 0160 0161 0162 //This code allows the fitsTools to start in a closed state 0163 fitsSplitter->setSizes(QList<int>() << 0 << m_View->width() ); 0164 0165 vlayout->addWidget(fitsSplitter); 0166 0167 stretchUI.reset(new FITSStretchUI(m_View, nullptr)); 0168 vlayout->addWidget(stretchUI.get()); 0169 0170 connect(fitsSplitter, &QSplitter::splitterMoved, m_HistogramEditor, &FITSHistogramEditor::resizePlot); 0171 0172 setLayout(vlayout); 0173 connect(m_View.get(), &FITSView::newStatus, this, &FITSTab::newStatus); 0174 connect(m_View.get(), &FITSView::debayerToggled, this, &FITSTab::debayerToggled); 0175 connect(m_View.get(), &FITSView::updated, this, &FITSTab::updated); 0176 0177 // On Failure to load 0178 connect(m_View.get(), &FITSView::failed, this, &FITSTab::failed); 0179 0180 return true; 0181 } 0182 0183 // returns false if no setup needed. 0184 return false; 0185 } 0186 0187 void FITSTab::loadFile(const QUrl &imageURL, FITSMode mode, FITSScale filter) 0188 { 0189 // check if the address points to an appropriate address 0190 if (imageURL.isEmpty() || !imageURL.isValid() || !QFileInfo::exists(imageURL.toLocalFile())) 0191 return; 0192 0193 if (setupView(mode, filter)) 0194 { 0195 0196 // On Success loading image 0197 connect(m_View.get(), &FITSView::loaded, this, [&]() 0198 { 0199 processData(); 0200 emit loaded(); 0201 }); 0202 0203 connect(m_View.get(), &FITSView::updated, this, &FITSTab::updated); 0204 } 0205 else 0206 // update tab text 0207 modifyFITSState(true, imageURL); 0208 0209 currentURL = imageURL; 0210 0211 m_View->setFilter(filter); 0212 0213 m_View->loadFile(imageURL.toLocalFile()); 0214 } 0215 0216 bool FITSTab::shouldComputeHFR() const 0217 { 0218 if (viewer->isStarsMarked()) 0219 return true; 0220 if (!Options::autoHFR()) 0221 return false; 0222 return ((!m_View.isNull()) && (m_View->getMode() == FITS_NORMAL)); 0223 } 0224 0225 void FITSTab::processData() 0226 { 0227 const QSharedPointer<FITSData> &imageData = m_View->imageData(); 0228 0229 m_HistogramEditor->setImageData(imageData); 0230 0231 // Only construct histogram if it is actually visible 0232 // Otherwise wait until histogram is needed before creating it. 0233 // if (fitsSplitter->sizes().at(0) != 0 && !imageData->isHistogramConstructed() && 0234 // !Options::nonLinearHistogram()) 0235 // { 0236 // imageData->constructHistogram(); 0237 // } 0238 0239 if (shouldComputeHFR()) 0240 { 0241 m_View->searchStars(); 0242 qCDebug(KSTARS_FITS) << "FITS HFR:" << imageData->getHFR(); 0243 } 0244 0245 displayStats(); 0246 0247 loadFITSHeader(); 0248 0249 // Don't add it to the list if it is already there 0250 if (recentImages->findItems(currentURL.toLocalFile(), Qt::MatchExactly).count() == 0) 0251 { 0252 //Don't add it to the list if it is a preview 0253 if(!imageData->filename().startsWith(QDir::tempPath())) 0254 { 0255 disconnect(recentImages, &QListWidget::currentRowChanged, this, 0256 &FITSTab::selectRecentFITS); 0257 recentImages->addItem(imageData->filename()); 0258 recentImages->setCurrentRow(recentImages->count() - 1); 0259 connect(recentImages, &QListWidget::currentRowChanged, this, 0260 &FITSTab::selectRecentFITS); 0261 } 0262 } 0263 0264 // This could both compute the HFRs and setup the graphics, however, 0265 // if shouldComputeHFR() above is true, then that will compute the HFRs 0266 // and this would notice that and just setup graphics. They are separated 0267 // for the case where the graphics is not desired. 0268 if (viewer->isStarsMarked()) 0269 { 0270 m_View->toggleStars(true); 0271 m_View->updateFrame(); 0272 } 0273 0274 // if (Options::nonLinearHistogram()) 0275 // m_HistogramEditor->createNonLinearHistogram(); 0276 0277 stretchUI->generateHistogram(); 0278 } 0279 0280 bool FITSTab::loadData(const QSharedPointer<FITSData> &data, FITSMode mode, FITSScale filter) 0281 { 0282 setupView(mode, filter); 0283 0284 // Empty URL 0285 currentURL = QUrl(); 0286 0287 if (viewer->isStarsMarked()) 0288 { 0289 m_View->toggleStars(true); 0290 //view->updateFrame(); 0291 } 0292 0293 m_View->setFilter(filter); 0294 0295 if (!m_View->loadData(data)) 0296 { 0297 // On Failure to load 0298 // connect(view.get(), &FITSView::failed, this, &FITSTab::failed); 0299 return false; 0300 } 0301 0302 processData(); 0303 return true; 0304 } 0305 0306 void FITSTab::modifyFITSState(bool clean, const QUrl &imageURL) 0307 { 0308 if (clean) 0309 { 0310 if (undoStack->isClean() == false) 0311 undoStack->setClean(); 0312 0313 mDirty = false; 0314 } 0315 else 0316 mDirty = true; 0317 0318 emit changeStatus(clean, imageURL); 0319 } 0320 0321 bool FITSTab::saveImage(const QString &filename) 0322 { 0323 return m_View->saveImage(filename); 0324 } 0325 0326 void FITSTab::copyFITS() 0327 { 0328 QApplication::clipboard()->setImage(m_View->getDisplayImage()); 0329 } 0330 0331 void FITSTab::histoFITS() 0332 { 0333 fitsTools->setCurrentIndex(1); 0334 if(m_View->width() > 200) 0335 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200); 0336 else 0337 fitsSplitter->setSizes(QList<int>() << 50 << 50); 0338 } 0339 0340 void FITSTab::displayStats(bool roi) 0341 { 0342 const QSharedPointer<FITSData> &imageData = m_View->imageData(); 0343 0344 stat.statsTable->item(STAT_WIDTH, 0)->setText(QString::number(imageData->width(roi))); 0345 stat.statsTable->item(STAT_HEIGHT, 0)->setText(QString::number(imageData->height(roi))); 0346 stat.statsTable->item(STAT_BITPIX, 0)->setText(QString::number(imageData->bpp())); 0347 0348 if (!roi) 0349 stat.statsTable->item(STAT_HFR, 0)->setText(QString::number(imageData->getHFR(), 'f', 3)); 0350 else 0351 stat.statsTable->item(STAT_HFR, 0)->setText("---"); 0352 0353 if (imageData->channels() == 1) 0354 { 0355 for (int i = STAT_MIN; i <= STAT_STDDEV; i++) 0356 { 0357 if (stat.statsTable->columnSpan(i, 0) != 3) 0358 stat.statsTable->setSpan(i, 0, 1, 3); 0359 } 0360 0361 stat.statsTable->horizontalHeaderItem(0)->setText(i18n("Value")); 0362 stat.statsTable->hideColumn(1); 0363 stat.statsTable->hideColumn(2); 0364 } 0365 else 0366 { 0367 for (int i = STAT_MIN; i <= STAT_STDDEV; i++) 0368 { 0369 if (stat.statsTable->columnSpan(i, 0) != 1) 0370 stat.statsTable->setSpan(i, 0, 1, 1); 0371 } 0372 0373 stat.statsTable->horizontalHeaderItem(0)->setText(i18nc("Red", "R")); 0374 stat.statsTable->showColumn(1); 0375 stat.statsTable->showColumn(2); 0376 } 0377 0378 if (!Options::nonLinearHistogram() && !imageData->isHistogramConstructed()) 0379 imageData->constructHistogram(); 0380 0381 for (int i = 0; i < imageData->channels(); i++) 0382 { 0383 stat.statsTable->item(STAT_MIN, i)->setText(QString::number(imageData->getMin(i, roi), 'f', 3)); 0384 stat.statsTable->item(STAT_MAX, i)->setText(QString::number(imageData->getMax(i, roi), 'f', 3)); 0385 stat.statsTable->item(STAT_MEAN, i)->setText(QString::number(imageData->getMean(i, roi), 'f', 3)); 0386 stat.statsTable->item(STAT_MEDIAN, i)->setText(QString::number(imageData->getMedian(i, roi), 'f', 3)); 0387 stat.statsTable->item(STAT_STDDEV, i)->setText(QString::number(imageData->getStdDev(i, roi), 'f', 3)); 0388 } 0389 } 0390 0391 void FITSTab::statFITS() 0392 { 0393 fitsTools->setCurrentIndex(0); 0394 if(m_View->width() > 200) 0395 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200); 0396 else 0397 fitsSplitter->setSizes(QList<int>() << 50 << 50); 0398 displayStats(); 0399 } 0400 0401 void FITSTab::loadFITSHeader() 0402 { 0403 const QSharedPointer<FITSData> &imageData = m_View->imageData(); 0404 0405 int nkeys = imageData->getRecords().size(); 0406 int counter = 0; 0407 header.tableWidget->setRowCount(nkeys); 0408 for (const auto &oneRecord : imageData->getRecords()) 0409 { 0410 QTableWidgetItem *tempItem = new QTableWidgetItem(oneRecord.key); 0411 tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 0412 header.tableWidget->setItem(counter, 0, tempItem); 0413 tempItem = new QTableWidgetItem(oneRecord.value.toString()); 0414 tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 0415 header.tableWidget->setItem(counter, 1, tempItem); 0416 tempItem = new QTableWidgetItem(oneRecord.comment); 0417 tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 0418 header.tableWidget->setItem(counter, 2, tempItem); 0419 counter++; 0420 } 0421 0422 header.tableWidget->setColumnWidth(0, 100); 0423 header.tableWidget->setColumnWidth(1, 100); 0424 header.tableWidget->setColumnWidth(2, 250); 0425 } 0426 0427 void FITSTab::headerFITS() 0428 { 0429 fitsTools->setCurrentIndex(2); 0430 if(m_View->width() > 200) 0431 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200); 0432 else 0433 fitsSplitter->setSizes(QList<int>() << 50 << 50); 0434 } 0435 0436 bool FITSTab::saveFile() 0437 { 0438 QUrl backupCurrent = currentURL; 0439 QUrl currentDir(Options::fitsDir()); 0440 currentDir.setScheme("file"); 0441 0442 if (currentURL.toLocalFile().startsWith(QLatin1String("/tmp/")) || currentURL.toLocalFile().contains("/Temp")) 0443 currentURL.clear(); 0444 0445 // If no changes made, return. 0446 if (mDirty == false && !currentURL.isEmpty()) 0447 return false; 0448 0449 if (currentURL.isEmpty()) 0450 { 0451 QString selectedFilter; 0452 #ifdef Q_OS_OSX //For some reason, the other code caused KStars to crash on MacOS 0453 currentURL = 0454 QFileDialog::getSaveFileUrl(KStars::Instance(), i18nc("@title:window", "Save FITS"), currentDir, 0455 "Images (*.fits *.fits.gz *.fit *.xisf *.jpg *.jpeg *.png)"); 0456 #else 0457 currentURL = 0458 QFileDialog::getSaveFileUrl(KStars::Instance(), i18nc("@title:window", "Save FITS"), currentDir, 0459 "FITS (*.fits *.fits.gz *.fit);;XISF (*.xisf);;JPEG (*.jpg *.jpeg);;PNG (*.png)", &selectedFilter); 0460 #endif 0461 // if user presses cancel 0462 if (currentURL.isEmpty()) 0463 { 0464 currentURL = backupCurrent; 0465 return false; 0466 } 0467 0468 // If no extension is selected append one 0469 if (currentURL.toLocalFile().contains('.') == 0) 0470 { 0471 if (selectedFilter.contains("XISF")) 0472 currentURL.setPath(currentURL.toLocalFile() + ".xisf"); 0473 else if (selectedFilter.contains("JPEG")) 0474 currentURL.setPath(currentURL.toLocalFile() + ".jpg"); 0475 else if (selectedFilter.contains("PNG")) 0476 currentURL.setPath(currentURL.toLocalFile() + ".png"); 0477 else 0478 currentURL.setPath(currentURL.toLocalFile() + ".fits"); 0479 } 0480 } 0481 0482 if (currentURL.isValid()) 0483 { 0484 QString localFile = currentURL.toLocalFile(); 0485 // if (localFile.contains(".fit")) 0486 // localFile = "!" + localFile; 0487 0488 if (!saveImage(localFile)) 0489 { 0490 KSNotification::error(i18n("Image save error: %1", m_View->imageData()->getLastError()), i18n("Image Save")); 0491 return false; 0492 } 0493 0494 emit newStatus(i18n("File saved to %1", currentURL.url()), FITS_MESSAGE); 0495 modifyFITSState(); 0496 return true; 0497 } 0498 else 0499 { 0500 QString message = i18n("Invalid URL: %1", currentURL.url()); 0501 KSNotification::sorry(message, i18n("Invalid URL")); 0502 return false; 0503 } 0504 } 0505 0506 bool FITSTab::saveFileAs() 0507 { 0508 currentURL.clear(); 0509 return saveFile(); 0510 } 0511 0512 void FITSTab::ZoomIn() 0513 { 0514 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center()); 0515 m_View->ZoomIn(); 0516 m_View->cleanUpZoom(oldCenter); 0517 } 0518 0519 void FITSTab::ZoomOut() 0520 { 0521 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center()); 0522 m_View->ZoomOut(); 0523 m_View->cleanUpZoom(oldCenter); 0524 } 0525 0526 void FITSTab::ZoomDefault() 0527 { 0528 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center()); 0529 m_View->ZoomDefault(); 0530 m_View->cleanUpZoom(oldCenter); 0531 } 0532 0533 void FITSTab::tabPositionUpdated() 0534 { 0535 undoStack->setActive(true); 0536 emit newStatus(QString("%1%").arg(m_View->getCurrentZoom()), FITS_ZOOM); 0537 emit newStatus(QString("%1x%2").arg(m_View->imageData()->width()).arg(m_View->imageData()->height()), 0538 FITS_RESOLUTION); 0539 } 0540 0541 void FITSTab::setStretchValues(double shadows, double midtones, double highlights) 0542 { 0543 if (stretchUI) 0544 stretchUI->setStretchValues(shadows, midtones, highlights); 0545 } 0546 0547 namespace 0548 { 0549 const QList<SSolver::Parameters> getSSolverParametersList() 0550 { 0551 const QString savedOptionsProfiles = QDir(KSPaths::writableLocation( 0552 QStandardPaths::AppLocalDataLocation)).filePath("SavedAlignProfiles.ini"); 0553 0554 return QFile(savedOptionsProfiles).exists() ? 0555 StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles) : 0556 Ekos::getDefaultAlignOptionsProfiles(); 0557 } 0558 } // namespace 0559 0560 void FITSTab::setupSolver(bool extractOnly) 0561 { 0562 auto parameters = getSSolverParametersList().at(m_PlateSolveUI.kcfg_FitsSolverProfile->currentIndex()); 0563 parameters.search_radius = m_PlateSolveUI.kcfg_FitsSolverRadius->value(); 0564 if (extractOnly) 0565 { 0566 m_Solver.reset(new SolverUtils(parameters, parameters.solverTimeLimit, SSolver::EXTRACT), &QObject::deleteLater); 0567 connect(m_Solver.get(), &SolverUtils::done, this, &FITSTab::extractorDone, Qt::UniqueConnection); 0568 } 0569 else 0570 { 0571 m_Solver.reset(new SolverUtils(parameters, parameters.solverTimeLimit, SSolver::SOLVE), &QObject::deleteLater); 0572 connect(m_Solver.get(), &SolverUtils::done, this, &FITSTab::solverDone, Qt::UniqueConnection); 0573 } 0574 0575 const int imageWidth = m_View->imageData()->width(); 0576 const int imageHeight = m_View->imageData()->height(); 0577 if (m_PlateSolveUI.kcfg_FitsSolverUseScale->isChecked() && imageWidth != 0 && imageHeight != 0) 0578 { 0579 const double scale = m_PlateSolveUI.kcfg_FitsSolverScale->value(); 0580 double lowScale = scale * 0.8; 0581 double highScale = scale * 1.2; 0582 0583 // solver utils uses arcsecs per pixel only 0584 const int units = m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits->currentIndex(); 0585 if (units == SSolver::DEG_WIDTH) 0586 { 0587 lowScale = (lowScale * 3600) / std::max(imageWidth, imageHeight); 0588 highScale = (highScale * 3600) / std::min(imageWidth, imageHeight); 0589 } 0590 else if (units == SSolver::ARCMIN_WIDTH) 0591 { 0592 lowScale = (lowScale * 60) / std::max(imageWidth, imageHeight); 0593 highScale = (highScale * 60) / std::min(imageWidth, imageHeight); 0594 } 0595 0596 m_Solver->useScale(m_PlateSolveUI.kcfg_FitsSolverUseScale->isChecked(), lowScale, highScale); 0597 } 0598 else m_Solver->useScale(false, 0, 0); 0599 0600 if (m_PlateSolveUI.kcfg_FitsSolverUsePosition->isChecked()) 0601 { 0602 bool ok; 0603 const dms ra = m_PlateSolveUI.FitsSolverEstRA->createDms(&ok); 0604 bool ok2; 0605 const dms dec = m_PlateSolveUI.FitsSolverEstDec->createDms(&ok2); 0606 if (ok && ok2) 0607 m_Solver->usePosition(true, ra.Degrees(), dec.Degrees()); 0608 else 0609 m_Solver->usePosition(false, 0, 0); 0610 } 0611 else m_Solver->usePosition(false, 0, 0); 0612 } 0613 0614 // If it is currently solving an image, then cancel the solve. 0615 // Otherwise start solving. 0616 void FITSTab::extractImage() 0617 { 0618 if (m_Solver.get() && m_Solver->isRunning()) 0619 { 0620 m_PlateSolveUI.SolveButton->setText(i18n("Aborting...")); 0621 m_Solver->abort(); 0622 return; 0623 } 0624 m_PlateSolveUI.SolveButton->setText(i18n("Cancel")); 0625 0626 setupSolver(true); 0627 0628 m_PlateSolveUI.FitsSolverAngle->setText(""); 0629 m_PlateSolveUI.Solution1->setText(i18n("Extracting...")); 0630 m_PlateSolveUI.Solution2->setText(""); 0631 0632 m_Solver->runSolver(m_View->imageData()); 0633 } 0634 0635 void FITSTab::solveImage() 0636 { 0637 if (m_Solver.get() && m_Solver->isRunning()) 0638 { 0639 m_PlateSolveUI.SolveButton->setText(i18n("Aborting...")); 0640 m_Solver->abort(); 0641 return; 0642 } 0643 m_PlateSolveUI.SolveButton->setText(i18n("Cancel")); 0644 0645 setupSolver(false); 0646 0647 m_PlateSolveUI.Solution2->setText(i18n("Solving...")); 0648 0649 m_Solver->runSolver(m_View->imageData()); 0650 } 0651 0652 void FITSTab::extractorDone(bool timedOut, bool success, const FITSImage::Solution &solution, double elapsedSeconds) 0653 { 0654 Q_UNUSED(solution); 0655 disconnect(m_Solver.get(), &SolverUtils::done, this, &FITSTab::extractorDone); 0656 m_PlateSolveUI.Solution2->setText(""); 0657 0658 if (timedOut) 0659 { 0660 const QString result = i18n("Extractor timed out: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1)); 0661 m_PlateSolveUI.Solution1->setText(result); 0662 0663 // Can't run the solver. Just reset. 0664 m_PlateSolveUI.SolveButton->setText("Solve"); 0665 return; 0666 } 0667 else if (!success) 0668 { 0669 const QString result = i18n("Extractor failed: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1)); 0670 m_PlateSolveUI.Solution1->setText(result); 0671 0672 // Can't run the solver. Just reset. 0673 m_PlateSolveUI.SolveButton->setText(i18n("Solve")); 0674 return; 0675 } 0676 else 0677 { 0678 const QString starStr = i18n("Extracted %1 stars (%2 unfiltered) in %3s", 0679 m_Solver->getNumStarsFound(), 0680 m_Solver->getBackground().num_stars_detected, 0681 QString("%1").arg(elapsedSeconds, 0, 'f', 1)); 0682 m_PlateSolveUI.Solution1->setText(starStr); 0683 0684 // Set the stars in the FITSData object so the user can view them. 0685 const QList<FITSImage::Star> &starList = m_Solver->getStarList(); 0686 QList<Edge*> starCenters; 0687 starCenters.reserve(starList.size()); 0688 for (int i = 0; i < starList.size(); i++) 0689 { 0690 const auto &star = starList[i]; 0691 Edge *oneEdge = new Edge(); 0692 oneEdge->x = star.x; 0693 oneEdge->y = star.y; 0694 oneEdge->val = star.peak; 0695 oneEdge->sum = star.flux; 0696 oneEdge->HFR = star.HFR; 0697 oneEdge->width = star.a; 0698 oneEdge->numPixels = star.numPixels; 0699 if (star.a > 0) 0700 // See page 63 to find the ellipticity equation for SEP. 0701 // http://astroa.physics.metu.edu.tr/MANUALS/sextractor/Guide2source_extractor.pdf 0702 oneEdge->ellipticity = 1 - star.b / star.a; 0703 else 0704 oneEdge->ellipticity = 0; 0705 0706 starCenters.append(oneEdge); 0707 } 0708 m_View->imageData()->setStarCenters(starCenters); 0709 0710 // Now run the solver. 0711 solveImage(); 0712 } 0713 } 0714 0715 void FITSTab::solverDone(bool timedOut, bool success, const FITSImage::Solution &solution, double elapsedSeconds) 0716 { 0717 disconnect(m_Solver.get(), &SolverUtils::done, this, &FITSTab::solverDone); 0718 m_PlateSolveUI.SolveButton->setText("Solve"); 0719 0720 if (m_Solver->isRunning()) 0721 qCDebug(KSTARS_FITS) << "solverDone called, but it is still running."; 0722 0723 if (timedOut) 0724 { 0725 const QString result = i18n("Solver timed out: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1)); 0726 m_PlateSolveUI.Solution2->setText(result); 0727 } 0728 else if (!success) 0729 { 0730 const QString result = i18n("Solver failed: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1)); 0731 m_PlateSolveUI.Solution2->setText(result); 0732 } 0733 else 0734 { 0735 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false : true; 0736 m_View->imageData()->injectWCS(solution.orientation, solution.ra, solution.dec, solution.pixscale, eastToTheRight); 0737 m_View->imageData()->loadWCS(); 0738 0739 const QString result = QString("Solved in %1s").arg(elapsedSeconds, 0, 'f', 1); 0740 const double solverPA = KSUtils::rotationToPositionAngle(solution.orientation); 0741 m_PlateSolveUI.FitsSolverAngle->setText(QString("%1ยบ").arg(solverPA, 0, 'f', 2)); 0742 0743 // Set the scale widget to the current result 0744 const int imageWidth = m_View->imageData()->width(); 0745 const int units = m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits->currentIndex(); 0746 if (units == SSolver::DEG_WIDTH) 0747 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(solution.pixscale * imageWidth / 3600.0); 0748 else if (units == SSolver::ARCMIN_WIDTH) 0749 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(solution.pixscale * imageWidth / 60.0); 0750 else 0751 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(solution.pixscale); 0752 0753 // Set the ra and dec widgets to the current result 0754 m_PlateSolveUI.FitsSolverEstRA->show(dms(solution.ra)); 0755 m_PlateSolveUI.FitsSolverEstDec->show(dms(solution.dec)); 0756 0757 m_PlateSolveUI.Solution2->setText(result); 0758 } 0759 } 0760 0761 void FITSTab::initSolverUI() 0762 { 0763 // Init the profiles combo box. 0764 const QList<SSolver::Parameters> optionsList = getSSolverParametersList(); 0765 m_PlateSolveUI.kcfg_FitsSolverProfile->clear(); 0766 for(auto ¶m : optionsList) 0767 m_PlateSolveUI.kcfg_FitsSolverProfile->addItem(param.listName); 0768 0769 // Restore the stored options. 0770 m_PlateSolveUI.kcfg_FitsSolverProfile->setCurrentIndex(Options::fitsSolverProfile()); 0771 0772 m_PlateSolveUI.kcfg_FitsSolverUseScale->setChecked(Options::fitsSolverUseScale()); 0773 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(Options::fitsSolverScale()); 0774 m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits->setCurrentIndex(Options::fitsSolverImageScaleUnits()); 0775 0776 m_PlateSolveUI.kcfg_FitsSolverUsePosition->setChecked(Options::fitsSolverUsePosition()); 0777 m_PlateSolveUI.kcfg_FitsSolverRadius->setValue(Options::fitsSolverRadius()); 0778 0779 m_PlateSolveUI.FitsSolverEstRA->setUnits(dmsBox::HOURS); 0780 m_PlateSolveUI.FitsSolverEstDec->setUnits(dmsBox::DEGREES); 0781 0782 // Save the values of user controls when the user changes them. 0783 connect(m_PlateSolveUI.kcfg_FitsSolverProfile, QOverload<int>::of(&QComboBox::activated), [](int index) 0784 { 0785 Options::setFitsSolverProfile(index); 0786 }); 0787 connect(m_PlateSolveUI.kcfg_FitsSolverUseScale, &QCheckBox::stateChanged, this, [](int state) 0788 { 0789 Options::setFitsSolverUseScale(state); 0790 }); 0791 connect(m_PlateSolveUI.kcfg_FitsSolverScale, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [](double value) 0792 { 0793 Options::setFitsSolverScale(value); 0794 }); 0795 connect(m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits, QOverload<int>::of(&QComboBox::activated), [](int index) 0796 { 0797 Options::setFitsSolverImageScaleUnits(index); 0798 }); 0799 0800 connect(m_PlateSolveUI.kcfg_FitsSolverUsePosition, &QCheckBox::stateChanged, this, [](int state) 0801 { 0802 Options::setFitsSolverUsePosition(state); 0803 }); 0804 0805 connect(m_PlateSolveUI.kcfg_FitsSolverRadius, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [](double value) 0806 { 0807 Options::setFitsSolverRadius(value); 0808 }); 0809 connect(m_PlateSolveUI.UpdatePosition, &QPushButton::clicked, this, [&]() 0810 { 0811 const auto center = SkyMap::Instance()->getCenterPoint(); 0812 m_PlateSolveUI.FitsSolverEstRA->show(center.ra()); 0813 m_PlateSolveUI.FitsSolverEstDec->show(center.dec()); 0814 }); 0815 0816 // Warn if the user is not using the internal StellarSolver solver. 0817 const SSolver::SolverType type = static_cast<SSolver::SolverType>(Options::solverType()); 0818 if(type != SSolver::SOLVER_STELLARSOLVER) 0819 { 0820 m_PlateSolveUI.Solution2->setText(i18n("Warning! This tool only supports the internal StellarSolver solver.")); 0821 m_PlateSolveUI.Solution1->setText(i18n("Change to that in the Ekos Align options menu.")); 0822 } 0823 }