Kstars

fitstab.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "fitstab.h"
8
9#include "auxiliary/kspaths.h"
10#include "fitsdata.h"
11#include "fitshistogrameditor.h"
12#include "fitshistogramcommand.h"
13#include "fitsview.h"
14#include "fitsviewer.h"
15#include "ksnotification.h"
16#include "kstars.h"
17#include "Options.h"
18#include "ui_fitsheaderdialog.h"
19#include "ui_statform.h"
20#include "fitsstretchui.h"
21#include "skymap.h"
22#include <KMessageBox>
23#include <QtConcurrent>
24#include <QIcon>
25#include "ekos/auxiliary/stellarsolverprofile.h"
26#include "ekos/auxiliary/stellarsolverprofileeditor.h"
27
28#include <fits_debug.h>
29
30QPointer<Ekos::StellarSolverProfileEditor> FITSTab::m_ProfileEditor;
31QPointer<KConfigDialog> FITSTab::m_EditorDialog;
32QPointer<KPageWidgetItem> FITSTab::m_ProfileEditorPage;
33
34FITSTab::FITSTab(FITSViewer *parent) : QWidget(parent)
35{
36 viewer = parent;
37 undoStack = new QUndoStack(this);
38 undoStack->setUndoLimit(10);
39 undoStack->clear();
40 connect(undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(modifyFITSState(bool)));
41
42 m_PlateSolveWidget = new QDialog(this);
43 statWidget = new QDialog(this);
44 fitsHeaderDialog = new QDialog(this);
45 m_HistogramEditor = new FITSHistogramEditor(this);
46 connect(m_HistogramEditor, &FITSHistogramEditor::newHistogramCommand, this, [this](FITSHistogramCommand * command)
47 {
48 undoStack->push(command);
49 });
50}
51
52FITSTab::~FITSTab()
53{
54}
55
56void FITSTab::saveUnsaved()
57{
58 if (undoStack->isClean() || m_View->getMode() != FITS_NORMAL)
59 return;
60
61 QString caption = i18n("Save Changes to FITS?");
62 QString message = i18n("The current FITS file has unsaved changes. Would you like to save before closing it?");
63
64 int ans = KMessageBox::warningYesNoCancel(nullptr, message, caption, KStandardGuiItem::save(), KStandardGuiItem::discard());
65 if (ans == KMessageBox::Yes)
66 saveFile();
67 if (ans == KMessageBox::No)
68 {
69 undoStack->clear();
70 modifyFITSState();
71 }
72}
73
74void FITSTab::closeEvent(QCloseEvent *ev)
75{
76 saveUnsaved();
77
78 if (undoStack->isClean())
79 ev->accept();
80 else
81 ev->ignore();
82}
83QString FITSTab::getPreviewText() const
84{
85 return previewText;
86}
87
88void FITSTab::setPreviewText(const QString &value)
89{
90 previewText = value;
91}
92
93void FITSTab::selectRecentFITS(int i)
94{
95 loadFile(QUrl::fromLocalFile(recentImages->item(i)->text()));
96}
97
98void FITSTab::clearRecentFITS()
99{
100 disconnect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS);
101 recentImages->clear();
102 connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS);
103}
104
105bool FITSTab::setupView(FITSMode mode, FITSScale filter)
106{
107 if (m_View.isNull())
108 {
109 m_View.reset(new FITSView(this, mode, filter));
110 m_View->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
112
113 connect(m_View.get(), &FITSView::rectangleUpdated, this, [this](QRect roi)
114 {
115 displayStats(roi.isValid());
116 });
117 fitsSplitter = new QSplitter(Qt::Horizontal, this);
118 fitsTools = new QToolBox();
119
120 stat.setupUi(statWidget);
121 m_PlateSolveUI.setupUi(m_PlateSolveWidget);
122
123 m_PlateSolveUI.editProfile->setIcon(QIcon::fromTheme("document-edit"));
124 m_PlateSolveUI.editProfile->setAttribute(Qt::WA_LayoutUsesWidgetRect);
125
126 const QString EditorID = "FITSSolverProfileEditor";
127 if (!m_EditorDialog)
128 {
129 // These are static, shared by all FITS Viewer tabs.
130 m_EditorDialog = new KConfigDialog(nullptr, EditorID, Options::self());
131 m_ProfileEditor = new Ekos::StellarSolverProfileEditor(nullptr, Ekos::AlignProfiles, m_EditorDialog.data());
132 m_ProfileEditorPage = m_EditorDialog->addPage(m_ProfileEditor.data(),
133 i18n("FITS Viewer Solver Profiles Editor"));
134 }
135
136 connect(m_PlateSolveUI.editProfile, &QAbstractButton::clicked, this, [this, EditorID]
137 {
138 m_ProfileEditor->loadProfile(m_PlateSolveUI.kcfg_FitsSolverProfile->currentText());
139 KConfigDialog * d = KConfigDialog::exists(EditorID);
140 if(d)
141 {
142 d->setCurrentPage(m_ProfileEditorPage);
143 d->show();
144 }
145 });
146
147 connect(m_PlateSolveUI.SolveButton, &QPushButton::clicked, this, &FITSTab::extractImage);
148
149 for (int i = 0; i <= STAT_STDDEV; i++)
150 {
151 for (int j = 0; j < 3; j++)
152 {
153 stat.statsTable->setItem(i, j, new QTableWidgetItem());
154 stat.statsTable->item(i, j)->setTextAlignment(Qt::AlignHCenter);
155 }
156
157 // Set col span for items up to HFR
158 if (i <= STAT_HFR)
159 stat.statsTable->setSpan(i, 0, 1, 3);
160 }
161
162 fitsTools->addItem(statWidget, i18n("Statistics"));
163
164 fitsTools->addItem(m_PlateSolveWidget, i18n("Plate Solving"));
165 initSolverUI();
166
167 fitsTools->addItem(m_HistogramEditor, i18n("Histogram"));
168
169 header.setupUi(fitsHeaderDialog);
170 fitsTools->addItem(fitsHeaderDialog, i18n("FITS Header"));
171
173 QWidget *recentPanel = new QWidget(fitsSplitter);
174 recentPanel->setLayout(recentPanelLayout);
175 fitsTools->addItem(recentPanel, i18n("Recent Images"));
176 recentImages = new QListWidget(recentPanel);
177 recentPanelLayout->addWidget(recentImages);
178 QPushButton *clearRecent = new QPushButton(i18n("Clear"));
179 recentPanelLayout->addWidget(clearRecent);
180 connect(clearRecent, &QPushButton::pressed, this, &FITSTab::clearRecentFITS);
181 connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS);
182
183 QScrollArea *scrollFitsPanel = new QScrollArea(fitsSplitter);
184 scrollFitsPanel->setWidgetResizable(true);
185 scrollFitsPanel->setWidget(fitsTools);
186
187 fitsSplitter->addWidget(scrollFitsPanel);
188 fitsSplitter->addWidget(m_View.get());
189
190
191 //This code allows the fitsTools to start in a closed state
192 fitsSplitter->setSizes(QList<int>() << 0 << m_View->width() );
193
194 vlayout->addWidget(fitsSplitter);
195
196 stretchUI.reset(new FITSStretchUI(m_View, nullptr));
197 vlayout->addWidget(stretchUI.get());
198
199 connect(fitsSplitter, &QSplitter::splitterMoved, m_HistogramEditor, &FITSHistogramEditor::resizePlot);
200
202 connect(m_View.get(), &FITSView::newStatus, this, &FITSTab::newStatus);
203 connect(m_View.get(), &FITSView::debayerToggled, this, &FITSTab::debayerToggled);
204 connect(m_View.get(), &FITSView::updated, this, &FITSTab::updated);
205
206 // On Failure to load
207 connect(m_View.get(), &FITSView::failed, this, &FITSTab::failed);
208
209 return true;
210 }
211
212 // returns false if no setup needed.
213 return false;
214}
215
216void FITSTab::loadFile(const QUrl &imageURL, FITSMode mode, FITSScale filter)
217{
218 // check if the address points to an appropriate address
219 if (imageURL.isEmpty() || !imageURL.isValid() || !QFileInfo::exists(imageURL.toLocalFile()))
220 return;
221
222 if (setupView(mode, filter))
223 {
224
225 // On Success loading image
226 connect(m_View.get(), &FITSView::loaded, this, [&]()
227 {
228 processData();
229 emit loaded();
230 });
231
232 connect(m_View.get(), &FITSView::updated, this, &FITSTab::updated);
233 }
234 else
235 // update tab text
236 modifyFITSState(true, imageURL);
237
238 currentURL = imageURL;
239
240 m_View->setFilter(filter);
241
242 m_View->loadFile(imageURL.toLocalFile());
243}
244
245bool FITSTab::shouldComputeHFR() const
246{
247 if (viewer->isStarsMarked())
248 return true;
249 if (!Options::autoHFR())
250 return false;
251 return ((!m_View.isNull()) && (m_View->getMode() == FITS_NORMAL));
252}
253
254void FITSTab::processData()
255{
256 const QSharedPointer<FITSData> &imageData = m_View->imageData();
257
258 m_HistogramEditor->setImageData(imageData);
259
260 // Only construct histogram if it is actually visible
261 // Otherwise wait until histogram is needed before creating it.
262 // if (fitsSplitter->sizes().at(0) != 0 && !imageData->isHistogramConstructed() &&
263 // !Options::nonLinearHistogram())
264 // {
265 // imageData->constructHistogram();
266 // }
267
268 if (shouldComputeHFR())
269 {
270 m_View->searchStars();
271 qCDebug(KSTARS_FITS) << "FITS HFR:" << imageData->getHFR();
272 }
273
274 displayStats();
275
276 loadFITSHeader();
277
278 // Don't add it to the list if it is already there
279 if (recentImages->findItems(currentURL.toLocalFile(), Qt::MatchExactly).count() == 0)
280 {
281 //Don't add it to the list if it is a preview
282 if(!imageData->filename().startsWith(QDir::tempPath()))
283 {
284 disconnect(recentImages, &QListWidget::currentRowChanged, this,
285 &FITSTab::selectRecentFITS);
286 recentImages->addItem(imageData->filename());
287 recentImages->setCurrentRow(recentImages->count() - 1);
288 connect(recentImages, &QListWidget::currentRowChanged, this,
289 &FITSTab::selectRecentFITS);
290 }
291 }
292
293 // This could both compute the HFRs and setup the graphics, however,
294 // if shouldComputeHFR() above is true, then that will compute the HFRs
295 // and this would notice that and just setup graphics. They are separated
296 // for the case where the graphics is not desired.
297 if (viewer->isStarsMarked())
298 {
299 m_View->toggleStars(true);
300 m_View->updateFrame();
301 }
302
303 // if (Options::nonLinearHistogram())
304 // m_HistogramEditor->createNonLinearHistogram();
305
306 stretchUI->generateHistogram();
307}
308
309bool FITSTab::loadData(const QSharedPointer<FITSData> &data, FITSMode mode, FITSScale filter)
310{
311 setupView(mode, filter);
312
313 // Empty URL
314 currentURL = QUrl();
315
316 if (viewer->isStarsMarked())
317 {
318 m_View->toggleStars(true);
319 //view->updateFrame();
320 }
321
322 m_View->setFilter(filter);
323
324 if (!m_View->loadData(data))
325 {
326 // On Failure to load
327 // connect(view.get(), &FITSView::failed, this, &FITSTab::failed);
328 return false;
329 }
330
331 processData();
332 return true;
333}
334
335void FITSTab::modifyFITSState(bool clean, const QUrl &imageURL)
336{
337 if (clean)
338 {
339 if (undoStack->isClean() == false)
340 undoStack->setClean();
341
342 mDirty = false;
343 }
344 else
345 mDirty = true;
346
347 emit changeStatus(clean, imageURL);
348}
349
350bool FITSTab::saveImage(const QString &filename)
351{
352 return m_View->saveImage(filename);
353}
354
355void FITSTab::copyFITS()
356{
357 QApplication::clipboard()->setImage(m_View->getDisplayImage());
358}
359
360void FITSTab::histoFITS()
361{
362 fitsTools->setCurrentIndex(1);
363 if(m_View->width() > 200)
364 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200);
365 else
366 fitsSplitter->setSizes(QList<int>() << 50 << 50);
367}
368
369void FITSTab::displayStats(bool roi)
370{
371 const QSharedPointer<FITSData> &imageData = m_View->imageData();
372
373 stat.statsTable->item(STAT_WIDTH, 0)->setText(QString::number(imageData->width(roi)));
374 stat.statsTable->item(STAT_HEIGHT, 0)->setText(QString::number(imageData->height(roi)));
375 stat.statsTable->item(STAT_BITPIX, 0)->setText(QString::number(imageData->bpp()));
376
377 if (!roi)
378 stat.statsTable->item(STAT_HFR, 0)->setText(QString::number(imageData->getHFR(), 'f', 3));
379 else
380 stat.statsTable->item(STAT_HFR, 0)->setText("---");
381
382 if (imageData->channels() == 1)
383 {
384 for (int i = STAT_MIN; i <= STAT_STDDEV; i++)
385 {
386 if (stat.statsTable->columnSpan(i, 0) != 3)
387 stat.statsTable->setSpan(i, 0, 1, 3);
388 }
389
390 stat.statsTable->horizontalHeaderItem(0)->setText(i18n("Value"));
391 stat.statsTable->hideColumn(1);
392 stat.statsTable->hideColumn(2);
393 }
394 else
395 {
396 for (int i = STAT_MIN; i <= STAT_STDDEV; i++)
397 {
398 if (stat.statsTable->columnSpan(i, 0) != 1)
399 stat.statsTable->setSpan(i, 0, 1, 1);
400 }
401
402 stat.statsTable->horizontalHeaderItem(0)->setText(i18nc("Red", "R"));
403 stat.statsTable->showColumn(1);
404 stat.statsTable->showColumn(2);
405 }
406
407 if (!Options::nonLinearHistogram() && !imageData->isHistogramConstructed())
408 imageData->constructHistogram();
409
410 for (int i = 0; i < imageData->channels(); i++)
411 {
412 stat.statsTable->item(STAT_MIN, i)->setText(QString::number(imageData->getMin(i, roi), 'f', 3));
413 stat.statsTable->item(STAT_MAX, i)->setText(QString::number(imageData->getMax(i, roi), 'f', 3));
414 stat.statsTable->item(STAT_MEAN, i)->setText(QString::number(imageData->getMean(i, roi), 'f', 3));
415 stat.statsTable->item(STAT_MEDIAN, i)->setText(QString::number(imageData->getMedian(i, roi), 'f', 3));
416 stat.statsTable->item(STAT_STDDEV, i)->setText(QString::number(imageData->getStdDev(i, roi), 'f', 3));
417 }
418}
419
420void FITSTab::statFITS()
421{
422 fitsTools->setCurrentIndex(0);
423 if(m_View->width() > 200)
424 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200);
425 else
426 fitsSplitter->setSizes(QList<int>() << 50 << 50);
427 displayStats();
428}
429
430void FITSTab::loadFITSHeader()
431{
432 const QSharedPointer<FITSData> &imageData = m_View->imageData();
433
434 int nkeys = imageData->getRecords().size();
435 int counter = 0;
436 header.tableWidget->setRowCount(nkeys);
437 for (const auto &oneRecord : imageData->getRecords())
438 {
441 header.tableWidget->setItem(counter, 0, tempItem);
442 tempItem = new QTableWidgetItem(oneRecord.value.toString());
444 header.tableWidget->setItem(counter, 1, tempItem);
445 tempItem = new QTableWidgetItem(oneRecord.comment);
447 header.tableWidget->setItem(counter, 2, tempItem);
448 counter++;
449 }
450
451 header.tableWidget->setColumnWidth(0, 100);
452 header.tableWidget->setColumnWidth(1, 100);
453 header.tableWidget->setColumnWidth(2, 250);
454}
455
456void FITSTab::headerFITS()
457{
458 fitsTools->setCurrentIndex(2);
459 if(m_View->width() > 200)
460 fitsSplitter->setSizes(QList<int>() << 200 << m_View->width() - 200);
461 else
462 fitsSplitter->setSizes(QList<int>() << 50 << 50);
463}
464
465bool FITSTab::saveFile()
466{
467 QUrl backupCurrent = currentURL;
468 QUrl currentDir(Options::fitsDir());
469 currentDir.setScheme("file");
470
471 if (currentURL.toLocalFile().startsWith(QLatin1String("/tmp/")) || currentURL.toLocalFile().contains("/Temp"))
472 currentURL.clear();
473
474 // If no changes made, return.
475 if (mDirty == false && !currentURL.isEmpty())
476 return false;
477
478 if (currentURL.isEmpty())
479 {
481#ifdef Q_OS_OSX //For some reason, the other code caused KStars to crash on MacOS
482 currentURL =
483 QFileDialog::getSaveFileUrl(KStars::Instance(), i18nc("@title:window", "Save FITS"), currentDir,
484 "Images (*.fits *.fits.gz *.fit *.xisf *.jpg *.jpeg *.png)");
485#else
486 currentURL =
487 QFileDialog::getSaveFileUrl(KStars::Instance(), i18nc("@title:window", "Save FITS"), currentDir,
488 "FITS (*.fits *.fits.gz *.fit);;XISF (*.xisf);;JPEG (*.jpg *.jpeg);;PNG (*.png)", &selectedFilter);
489#endif
490 // if user presses cancel
491 if (currentURL.isEmpty())
492 {
493 currentURL = backupCurrent;
494 return false;
495 }
496
497 // If no extension is selected append one
498 if (currentURL.toLocalFile().contains('.') == 0)
499 {
500 if (selectedFilter.contains("XISF"))
501 currentURL.setPath(currentURL.toLocalFile() + ".xisf");
502 else if (selectedFilter.contains("JPEG"))
503 currentURL.setPath(currentURL.toLocalFile() + ".jpg");
504 else if (selectedFilter.contains("PNG"))
505 currentURL.setPath(currentURL.toLocalFile() + ".png");
506 else
507 currentURL.setPath(currentURL.toLocalFile() + ".fits");
508 }
509 }
510
511 if (currentURL.isValid())
512 {
513 QString localFile = currentURL.toLocalFile();
514 // if (localFile.contains(".fit"))
515 // localFile = "!" + localFile;
516
517 if (!saveImage(localFile))
518 {
519 KSNotification::error(i18n("Image save error: %1", m_View->imageData()->getLastError()), i18n("Image Save"));
520 return false;
521 }
522
523 emit newStatus(i18n("File saved to %1", currentURL.url()), FITS_MESSAGE);
524 modifyFITSState();
525 return true;
526 }
527 else
528 {
529 QString message = i18n("Invalid URL: %1", currentURL.url());
530 KSNotification::sorry(message, i18n("Invalid URL"));
531 return false;
532 }
533}
534
535bool FITSTab::saveFileAs()
536{
537 currentURL.clear();
538 return saveFile();
539}
540
541void FITSTab::ZoomIn()
542{
543 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center());
544 m_View->ZoomIn();
545 m_View->cleanUpZoom(oldCenter);
546}
547
548void FITSTab::ZoomOut()
549{
550 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center());
551 m_View->ZoomOut();
552 m_View->cleanUpZoom(oldCenter);
553}
554
555void FITSTab::ZoomDefault()
556{
557 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center());
558 m_View->ZoomDefault();
559 m_View->cleanUpZoom(oldCenter);
560}
561
562void FITSTab::tabPositionUpdated()
563{
564 undoStack->setActive(true);
565 emit newStatus(QString("%1%").arg(m_View->getCurrentZoom()), FITS_ZOOM);
566 emit newStatus(QString("%1x%2").arg(m_View->imageData()->width()).arg(m_View->imageData()->height()),
567 FITS_RESOLUTION);
568}
569
570void FITSTab::setStretchValues(double shadows, double midtones, double highlights)
571{
572 if (stretchUI)
573 stretchUI->setStretchValues(shadows, midtones, highlights);
574}
575
576namespace
577{
579{
581 switch(module)
582 {
583 case Ekos::AlignProfiles:
584 default:
585 savedProfiles = QDir(KSPaths::writableLocation(
586 QStandardPaths::AppLocalDataLocation)).filePath("SavedAlignProfiles.ini");
587 return QFile(savedProfiles).exists() ?
589 Ekos::getDefaultAlignOptionsProfiles();
590 break;
591 case Ekos::FocusProfiles:
592 savedProfiles = QDir(KSPaths::writableLocation(
593 QStandardPaths::AppLocalDataLocation)).filePath("SavedFocusProfiles.ini");
594 return QFile(savedProfiles).exists() ?
596 Ekos::getDefaultFocusOptionsProfiles();
597 break;
598 case Ekos::GuideProfiles:
599 savedProfiles = QDir(KSPaths::writableLocation(
600 QStandardPaths::AppLocalDataLocation)).filePath("SavedGuideProfiles.ini");
601 return QFile(savedProfiles).exists() ?
603 Ekos::getDefaultGuideOptionsProfiles();
604 break;
605 case Ekos::HFRProfiles:
606 savedProfiles = QDir(KSPaths::writableLocation(
607 QStandardPaths::AppLocalDataLocation)).filePath("SavedHFRProfiles.ini");
608 return QFile(savedProfiles).exists() ?
610 Ekos::getDefaultHFROptionsProfiles();
611 break;
612 }
613}
614} // namespace
615
616void FITSTab::setupSolver(bool extractOnly)
617{
618 auto parameters = getSSolverParametersList(static_cast<Ekos::ProfileGroup>(Options::fitsSolverModule())).at(
619 m_PlateSolveUI.kcfg_FitsSolverProfile->currentIndex());
620 parameters.search_radius = m_PlateSolveUI.kcfg_FitsSolverRadius->value();
621 if (extractOnly)
622 {
623 m_Solver.reset(new SolverUtils(parameters, parameters.solverTimeLimit, SSolver::EXTRACT), &QObject::deleteLater);
624 connect(m_Solver.get(), &SolverUtils::done, this, &FITSTab::extractorDone, Qt::UniqueConnection);
625 }
626 else
627 {
628 m_Solver.reset(new SolverUtils(parameters, parameters.solverTimeLimit, SSolver::SOLVE), &QObject::deleteLater);
629 connect(m_Solver.get(), &SolverUtils::done, this, &FITSTab::solverDone, Qt::UniqueConnection);
630 }
631
632 const int imageWidth = m_View->imageData()->width();
633 const int imageHeight = m_View->imageData()->height();
634 if (m_PlateSolveUI.kcfg_FitsSolverUseScale->isChecked() && imageWidth != 0 && imageHeight != 0)
635 {
636 const double scale = m_PlateSolveUI.kcfg_FitsSolverScale->value();
637 double lowScale = scale * 0.8;
638 double highScale = scale * 1.2;
639
640 // solver utils uses arcsecs per pixel only
641 const int units = m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits->currentIndex();
642 if (units == SSolver::DEG_WIDTH)
643 {
644 lowScale = (lowScale * 3600) / std::max(imageWidth, imageHeight);
645 highScale = (highScale * 3600) / std::min(imageWidth, imageHeight);
646 }
647 else if (units == SSolver::ARCMIN_WIDTH)
648 {
649 lowScale = (lowScale * 60) / std::max(imageWidth, imageHeight);
650 highScale = (highScale * 60) / std::min(imageWidth, imageHeight);
651 }
652
653 m_Solver->useScale(m_PlateSolveUI.kcfg_FitsSolverUseScale->isChecked(), lowScale, highScale);
654 }
655 else m_Solver->useScale(false, 0, 0);
656
657 if (m_PlateSolveUI.kcfg_FitsSolverUsePosition->isChecked())
658 {
659 bool ok;
660 const dms ra = m_PlateSolveUI.FitsSolverEstRA->createDms(&ok);
661 bool ok2;
662 const dms dec = m_PlateSolveUI.FitsSolverEstDec->createDms(&ok2);
663 if (ok && ok2)
664 m_Solver->usePosition(true, ra.Degrees(), dec.Degrees());
665 else
666 m_Solver->usePosition(false, 0, 0);
667 }
668 else m_Solver->usePosition(false, 0, 0);
669}
670
671// If it is currently solving an image, then cancel the solve.
672// Otherwise start solving.
673void FITSTab::extractImage()
674{
675 if (m_Solver.get() && m_Solver->isRunning())
676 {
677 m_PlateSolveUI.SolveButton->setText(i18n("Aborting..."));
678 m_Solver->abort();
679 return;
680 }
681 m_PlateSolveUI.SolveButton->setText(i18n("Cancel"));
682
683 setupSolver(true);
684
685 m_PlateSolveUI.FitsSolverAngle->setText("");
686 m_PlateSolveUI.Solution1->setText(i18n("Extracting..."));
687 m_PlateSolveUI.Solution2->setText("");
688
689 m_Solver->runSolver(m_View->imageData());
690}
691
692void FITSTab::solveImage()
693{
694 if (m_Solver.get() && m_Solver->isRunning())
695 {
696 m_PlateSolveUI.SolveButton->setText(i18n("Aborting..."));
697 m_Solver->abort();
698 return;
699 }
700 m_PlateSolveUI.SolveButton->setText(i18n("Cancel"));
701
702 setupSolver(false);
703
704 m_PlateSolveUI.Solution2->setText(i18n("Solving..."));
705
706 m_Solver->runSolver(m_View->imageData());
707}
708
709void FITSTab::extractorDone(bool timedOut, bool success, const FITSImage::Solution &solution, double elapsedSeconds)
710{
711 Q_UNUSED(solution);
712 disconnect(m_Solver.get(), &SolverUtils::done, this, &FITSTab::extractorDone);
713 m_PlateSolveUI.Solution2->setText("");
714
715 if (timedOut)
716 {
717 const QString result = i18n("Extractor timed out: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1));
718 m_PlateSolveUI.Solution1->setText(result);
719
720 // Can't run the solver. Just reset.
721 m_PlateSolveUI.SolveButton->setText("Solve");
722 return;
723 }
724 else if (!success)
725 {
726 const QString result = i18n("Extractor failed: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1));
727 m_PlateSolveUI.Solution1->setText(result);
728
729 // Can't run the solver. Just reset.
730 m_PlateSolveUI.SolveButton->setText(i18n("Solve"));
731 return;
732 }
733 else
734 {
735 const QString starStr = i18n("Extracted %1 stars (%2 unfiltered) in %3s",
736 m_Solver->getNumStarsFound(),
737 m_Solver->getBackground().num_stars_detected,
738 QString("%1").arg(elapsedSeconds, 0, 'f', 1));
739 m_PlateSolveUI.Solution1->setText(starStr);
740
741 // Set the stars in the FITSData object so the user can view them.
742 const QList<FITSImage::Star> &starList = m_Solver->getStarList();
743 QList<Edge*> starCenters;
744 starCenters.reserve(starList.size());
745 for (int i = 0; i < starList.size(); i++)
746 {
747 const auto &star = starList[i];
748 Edge *oneEdge = new Edge();
749 oneEdge->x = star.x;
750 oneEdge->y = star.y;
751 oneEdge->val = star.peak;
752 oneEdge->sum = star.flux;
753 oneEdge->HFR = star.HFR;
754 oneEdge->width = star.a;
755 oneEdge->numPixels = star.numPixels;
756 if (star.a > 0)
757 // See page 63 to find the ellipticity equation for SEP.
758 // http://astroa.physics.metu.edu.tr/MANUALS/sextractor/Guide2source_extractor.pdf
759 oneEdge->ellipticity = 1 - star.b / star.a;
760 else
761 oneEdge->ellipticity = 0;
762
763 starCenters.append(oneEdge);
764 }
765 m_View->imageData()->setStarCenters(starCenters);
766 m_View->updateFrame();
767
768 // Now run the solver.
769 solveImage();
770 }
771}
772
773void FITSTab::solverDone(bool timedOut, bool success, const FITSImage::Solution &solution, double elapsedSeconds)
774{
775 disconnect(m_Solver.get(), &SolverUtils::done, this, &FITSTab::solverDone);
776 m_PlateSolveUI.SolveButton->setText("Solve");
777
778 if (m_Solver->isRunning())
779 qCDebug(KSTARS_FITS) << "solverDone called, but it is still running.";
780
781 if (timedOut)
782 {
783 const QString result = i18n("Solver timed out: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1));
784 m_PlateSolveUI.Solution2->setText(result);
785 }
786 else if (!success)
787 {
788 const QString result = i18n("Solver failed: %1s", QString("%L1").arg(elapsedSeconds, 0, 'f', 1));
789 m_PlateSolveUI.Solution2->setText(result);
790 }
791 else
792 {
793 const bool eastToTheRight = solution.parity == FITSImage::POSITIVE ? false : true;
794 m_View->imageData()->injectWCS(solution.orientation, solution.ra, solution.dec, solution.pixscale, eastToTheRight);
795 m_View->imageData()->loadWCS();
796
797 const QString result = QString("Solved in %1s").arg(elapsedSeconds, 0, 'f', 1);
798 const double solverPA = KSUtils::rotationToPositionAngle(solution.orientation);
799 m_PlateSolveUI.FitsSolverAngle->setText(QString("%1ยบ").arg(solverPA, 0, 'f', 2));
800
801 // Set the scale widget to the current result
802 const int imageWidth = m_View->imageData()->width();
803 const int units = m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits->currentIndex();
804 if (units == SSolver::DEG_WIDTH)
805 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(solution.pixscale * imageWidth / 3600.0);
806 else if (units == SSolver::ARCMIN_WIDTH)
807 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(solution.pixscale * imageWidth / 60.0);
808 else
809 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(solution.pixscale);
810
811 // Set the ra and dec widgets to the current result
812 m_PlateSolveUI.FitsSolverEstRA->show(dms(solution.ra));
813 m_PlateSolveUI.FitsSolverEstDec->show(dms(solution.dec));
814
815 m_PlateSolveUI.Solution2->setText(result);
816 }
817}
818
819// Each module can default to its own profile index. These two methods retrieves and saves
820// the values in a JSON string using an Options variable.
821int FITSTab::getProfileIndex(int moduleIndex)
822{
823 if (moduleIndex < 0 || moduleIndex >= Ekos::ProfileGroupNames.size())
824 return 0;
825 const QString moduleName = Ekos::ProfileGroupNames[moduleIndex];
826 const QString str = Options::fitsSolverProfileIndeces();
828 if (doc.isNull() || !doc.isObject())
829 return 0;
830 const QJsonObject indeces = doc.object();
831 return indeces[moduleName].toString().toInt();
832}
833
834void FITSTab::setProfileIndex(int moduleIndex, int profileIndex)
835{
836 if (moduleIndex < 0 || moduleIndex >= Ekos::ProfileGroupNames.size())
837 return;
838 QString str = Options::fitsSolverProfileIndeces();
840 if (doc.isNull() || !doc.isObject())
841 {
843 for (int i = 0; i < Ekos::ProfileGroupNames.size(); i++)
844 {
845 QString name = Ekos::ProfileGroupNames[i];
846 if (name == "Align")
847 initialIndeces[name] = QString::number(Options::solveOptionsProfile());
848 else if (name == "Guide")
849 initialIndeces[name] = QString::number(Options::guideOptionsProfile());
850 else if (name == "HFR")
851 initialIndeces[name] = QString::number(Options::hFROptionsProfile());
852 else // Focus has a weird setting, just default to 0
853 initialIndeces[name] = "0";
854 }
856 }
857
858 QJsonObject indeces = doc.object();
859 indeces[Ekos::ProfileGroupNames[moduleIndex]] = QString::number(profileIndex);
860 doc = QJsonDocument(indeces);
861 Options::setFitsSolverProfileIndeces(QString(doc.toJson()));
862}
863
864void FITSTab::setupProfiles(int moduleIndex)
865{
866 if (moduleIndex < 0 || moduleIndex >= Ekos::ProfileGroupNames.size())
867 return;
868 Ekos::ProfileGroup profileGroup = static_cast<Ekos::ProfileGroup>(moduleIndex);
869 Options::setFitsSolverModule(moduleIndex);
870
871 // Set up the profiles' menu.
873 m_PlateSolveUI.kcfg_FitsSolverProfile->clear();
874 for(auto &param : optionsList)
875 m_PlateSolveUI.kcfg_FitsSolverProfile->addItem(param.listName);
876
877 m_ProfileEditor->setProfileGroup(profileGroup, false);
878
879 // Restore the stored options.
880 m_PlateSolveUI.kcfg_FitsSolverProfile->setCurrentIndex(getProfileIndex(Options::fitsSolverModule()));
881
882 m_ProfileEditorPage->setHeader(QString("FITS Viewer Solver %1 Profiles Editor")
883 .arg(Ekos::ProfileGroupNames[moduleIndex]));
884}
885
886void FITSTab::initSolverUI()
887{
888 // Init the modules combo box.
889 m_PlateSolveUI.kcfg_FitsSolverModule->clear();
890 for (int i = 0; i < Ekos::ProfileGroupNames.size(); i++)
891 m_PlateSolveUI.kcfg_FitsSolverModule->addItem(Ekos::ProfileGroupNames[i]);
892 m_PlateSolveUI.kcfg_FitsSolverModule->setCurrentIndex(Options::fitsSolverModule());
893
894 setupProfiles(Options::fitsSolverModule());
895
896 // Change the profiles combo box whenever the modules combo changes
897 connect(m_PlateSolveUI.kcfg_FitsSolverModule, QOverload<int>::of(&QComboBox::activated), this, &FITSTab::setupProfiles);
898
899 m_PlateSolveUI.kcfg_FitsSolverUseScale->setChecked(Options::fitsSolverUseScale());
900 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(Options::fitsSolverScale());
901 m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits->setCurrentIndex(Options::fitsSolverImageScaleUnits());
902
903 m_PlateSolveUI.kcfg_FitsSolverUsePosition->setChecked(Options::fitsSolverUsePosition());
904 m_PlateSolveUI.kcfg_FitsSolverRadius->setValue(Options::fitsSolverRadius());
905
906 m_PlateSolveUI.FitsSolverEstRA->setUnits(dmsBox::HOURS);
907 m_PlateSolveUI.FitsSolverEstDec->setUnits(dmsBox::DEGREES);
908
909 // Save the values of user controls when the user changes them.
910 connect(m_PlateSolveUI.kcfg_FitsSolverProfile, QOverload<int>::of(&QComboBox::activated), [this](int index)
911 {
912 setProfileIndex(m_PlateSolveUI.kcfg_FitsSolverModule->currentIndex(), index);
913 });
914
915 connect(m_PlateSolveUI.kcfg_FitsSolverUseScale, &QCheckBox::stateChanged, this, [](int state)
916 {
917 Options::setFitsSolverUseScale(state);
918 });
919 connect(m_PlateSolveUI.kcfg_FitsSolverScale, QOverload<double>::of(&QDoubleSpinBox::valueChanged), [](double value)
920 {
921 Options::setFitsSolverScale(value);
922 });
923 connect(m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits, QOverload<int>::of(&QComboBox::activated), [](int index)
924 {
925 Options::setFitsSolverImageScaleUnits(index);
926 });
927
928 connect(m_PlateSolveUI.kcfg_FitsSolverUsePosition, &QCheckBox::stateChanged, this, [](int state)
929 {
930 Options::setFitsSolverUsePosition(state);
931 });
932
933 connect(m_PlateSolveUI.kcfg_FitsSolverRadius, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, [](double value)
934 {
935 Options::setFitsSolverRadius(value);
936 });
937 connect(m_PlateSolveUI.UpdatePosition, &QPushButton::clicked, this, [&]()
938 {
939 const auto center = SkyMap::Instance()->getCenterPoint();
940 m_PlateSolveUI.FitsSolverEstRA->show(center.ra());
941 m_PlateSolveUI.FitsSolverEstDec->show(center.dec());
942 });
943
944 // Warn if the user is not using the internal StellarSolver solver.
945 const SSolver::SolverType type = static_cast<SSolver::SolverType>(Options::solverType());
946 if(type != SSolver::SOLVER_STELLARSOLVER)
947 {
948 m_PlateSolveUI.Solution2->setText(i18n("Warning! This tool only supports the internal StellarSolver solver."));
949 m_PlateSolveUI.Solution1->setText(i18n("Change to that in the Ekos Align options menu."));
950 }
951}
Primary window to view monochrome and color FITS images.
Definition fitsviewer.h:50
static KStars * Instance()
Definition kstars.h:123
An angle, stored as degrees, but expressible in many ways.
Definition dms.h:38
const double & Degrees() const
Definition dms.h:141
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:78
KGuiItem save()
KGuiItem discard()
QString name(StandardShortcut id)
void clicked(bool checked)
void stateChanged(int state)
void setImage(const QImage &image, Mode mode)
void activated(int index)
QString filePath(const QString &fileName) const const
QString tempPath()
void valueChanged(double d)
bool exists(const QString &fileName)
QUrl getSaveFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, Options options, const QStringList &supportedSchemes)
bool exists() const const
Int toInt() const const
QClipboard * clipboard()
QIcon fromTheme(const QString &name)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
bool isNull() const const
bool isObject() const const
QJsonObject object() const const
QByteArray toJson(JsonFormat format) const const
void currentRowChanged(int currentRow)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
bool disconnect(const QMetaObject::Connection &connection)
T * get() const const
bool isNull() const const
void splitterMoved(int pos, int index)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
AlignHCenter
UniqueConnection
ItemIsSelectable
MatchExactly
Horizontal
WA_LayoutUsesWidgetRect
QTextStream & dec(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setActive(bool active)
void clear()
bool isClean() const const
void setClean()
void clear()
QUrl fromLocalFile(const QString &localFile)
bool isEmpty() const const
bool isValid() const const
void setPath(const QString &path, ParsingMode mode)
QString toLocalFile() const const
QString url(FormattingOptions options) const const
QWidget(QWidget *parent, Qt::WindowFlags f)
void setLayout(QLayout *layout)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.