9#include "auxiliary/kspaths.h"
11#include "fitshistogrameditor.h"
12#include "fitshistogramcommand.h"
14#include "fitsviewer.h"
15#include "ksnotification.h"
18#include "ui_fitsheaderdialog.h"
19#include "ui_statform.h"
20#include "fitsstretchui.h"
23#include <QtConcurrent>
25#include "ekos/auxiliary/stellarsolverprofile.h"
26#include "ekos/auxiliary/stellarsolverprofileeditor.h"
28#include <fits_debug.h>
38 undoStack->setUndoLimit(10);
40 connect(undoStack,
SIGNAL(cleanChanged(
bool)),
this,
SLOT(modifyFITSState(
bool)));
42 m_PlateSolveWidget =
new QDialog(
this);
44 fitsHeaderDialog =
new QDialog(
this);
45 m_HistogramEditor =
new FITSHistogramEditor(
this);
46 connect(m_HistogramEditor, &FITSHistogramEditor::newHistogramCommand,
this, [
this](FITSHistogramCommand * command)
48 undoStack->push(command);
56void FITSTab::saveUnsaved()
58 if (undoStack->
isClean() || m_View->getMode() != FITS_NORMAL)
62 QString message =
i18n(
"The current FITS file has unsaved changes. Would you like to save before closing it?");
65 if (
ans == KMessageBox::Yes)
67 if (
ans == KMessageBox::No)
83QString FITSTab::getPreviewText()
const
88void FITSTab::setPreviewText(
const QString &value)
93void FITSTab::selectRecentFITS(
int i)
98void FITSTab::clearRecentFITS()
101 recentImages->clear();
105bool FITSTab::setupView(FITSMode mode, FITSScale filter)
109 m_View.
reset(
new FITSView(
this, mode, filter));
115 displayStats(roi.isValid());
120 stat.setupUi(statWidget);
121 m_PlateSolveUI.setupUi(m_PlateSolveWidget);
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"));
138 m_ProfileEditor->loadProfile(m_PlateSolveUI.kcfg_FitsSolverProfile->currentText());
139 KConfigDialog * d = KConfigDialog::exists(EditorID);
142 d->setCurrentPage(m_ProfileEditorPage);
149 for (
int i = 0; i <= STAT_STDDEV; i++)
151 for (
int j = 0;
j < 3;
j++)
159 stat.statsTable->setSpan(i, 0, 1, 3);
162 fitsTools->addItem(statWidget,
i18n(
"Statistics"));
164 fitsTools->addItem(m_PlateSolveWidget,
i18n(
"Plate Solving"));
167 fitsTools->addItem(m_HistogramEditor,
i18n(
"Histogram"));
169 header.setupUi(fitsHeaderDialog);
170 fitsTools->addItem(fitsHeaderDialog,
i18n(
"FITS Header"));
188 fitsSplitter->addWidget(m_View.
get());
192 fitsSplitter->setSizes(
QList<int>() << 0 << m_View->width() );
194 vlayout->addWidget(fitsSplitter);
196 stretchUI.reset(
new FITSStretchUI(m_View,
nullptr));
197 vlayout->addWidget(stretchUI.get());
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);
207 connect(m_View.
get(), &FITSView::failed,
this, &FITSTab::failed);
216void FITSTab::loadFile(
const QUrl &
imageURL, FITSMode mode, FITSScale filter)
222 if (setupView(mode, filter))
226 connect(m_View.
get(), &FITSView::loaded,
this, [&]()
232 connect(m_View.
get(), &FITSView::updated,
this, &FITSTab::updated);
240 m_View->setFilter(filter);
242 m_View->loadFile(
imageURL.toLocalFile());
245bool FITSTab::shouldComputeHFR()
const
247 if (viewer->isStarsMarked())
249 if (!Options::autoHFR())
251 return ((!m_View.
isNull()) && (m_View->getMode() == FITS_NORMAL));
254void FITSTab::processData()
258 m_HistogramEditor->setImageData(imageData);
268 if (shouldComputeHFR())
270 m_View->searchStars();
271 qCDebug(
KSTARS_FITS) <<
"FITS HFR:" << imageData->getHFR();
285 &FITSTab::selectRecentFITS);
286 recentImages->addItem(imageData->filename());
287 recentImages->setCurrentRow(recentImages->count() - 1);
289 &FITSTab::selectRecentFITS);
297 if (viewer->isStarsMarked())
299 m_View->toggleStars(
true);
300 m_View->updateFrame();
306 stretchUI->generateHistogram();
311 setupView(mode, filter);
316 if (viewer->isStarsMarked())
318 m_View->toggleStars(
true);
322 m_View->setFilter(filter);
324 if (!m_View->loadData(data))
335void FITSTab::modifyFITSState(
bool clean,
const QUrl &
imageURL)
339 if (undoStack->
isClean() ==
false)
350bool FITSTab::saveImage(
const QString &filename)
352 return m_View->saveImage(filename);
355void FITSTab::copyFITS()
360void FITSTab::histoFITS()
362 fitsTools->setCurrentIndex(1);
363 if(m_View->width() > 200)
364 fitsSplitter->setSizes(
QList<int>() << 200 << m_View->width() - 200);
366 fitsSplitter->setSizes(
QList<int>() << 50 << 50);
369void FITSTab::displayStats(
bool roi)
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()));
378 stat.statsTable->item(STAT_HFR, 0)->setText(
QString::number(imageData->getHFR(),
'f', 3));
380 stat.statsTable->item(STAT_HFR, 0)->setText(
"---");
382 if (imageData->channels() == 1)
384 for (
int i = STAT_MIN; i <= STAT_STDDEV; i++)
386 if (stat.statsTable->columnSpan(i, 0) != 3)
387 stat.statsTable->setSpan(i, 0, 1, 3);
390 stat.statsTable->horizontalHeaderItem(0)->setText(
i18n(
"Value"));
391 stat.statsTable->hideColumn(1);
392 stat.statsTable->hideColumn(2);
396 for (
int i = STAT_MIN; i <= STAT_STDDEV; i++)
398 if (stat.statsTable->columnSpan(i, 0) != 1)
399 stat.statsTable->setSpan(i, 0, 1, 1);
402 stat.statsTable->horizontalHeaderItem(0)->setText(
i18nc(
"Red",
"R"));
403 stat.statsTable->showColumn(1);
404 stat.statsTable->showColumn(2);
407 if (!Options::nonLinearHistogram() && !imageData->isHistogramConstructed())
408 imageData->constructHistogram();
410 for (
int i = 0; i < imageData->channels(); i++)
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));
420void FITSTab::statFITS()
422 fitsTools->setCurrentIndex(0);
423 if(m_View->width() > 200)
424 fitsSplitter->setSizes(
QList<int>() << 200 << m_View->width() - 200);
426 fitsSplitter->setSizes(
QList<int>() << 50 << 50);
430void FITSTab::loadFITSHeader()
434 int nkeys = imageData->getRecords().size();
436 header.tableWidget->setRowCount(
nkeys);
437 for (
const auto &
oneRecord : imageData->getRecords())
451 header.tableWidget->setColumnWidth(0, 100);
452 header.tableWidget->setColumnWidth(1, 100);
453 header.tableWidget->setColumnWidth(2, 250);
456void FITSTab::headerFITS()
458 fitsTools->setCurrentIndex(2);
459 if(m_View->width() > 200)
460 fitsSplitter->setSizes(
QList<int>() << 200 << m_View->width() - 200);
462 fitsSplitter->setSizes(
QList<int>() << 50 << 50);
465bool FITSTab::saveFile()
468 QUrl currentDir(Options::fitsDir());
469 currentDir.setScheme(
"file");
475 if (mDirty ==
false && !currentURL.
isEmpty())
484 "Images (*.fits *.fits.gz *.fit *.xisf *.jpg *.jpeg *.png)");
488 "FITS (*.fits *.fits.gz *.fit);;XISF (*.xisf);;JPEG (*.jpg *.jpeg);;PNG (*.png)", &
selectedFilter);
519 KSNotification::error(
i18n(
"Image save error: %1", m_View->imageData()->getLastError()),
i18n(
"Image Save"));
523 emit newStatus(
i18n(
"File saved to %1", currentURL.
url()), FITS_MESSAGE);
530 KSNotification::sorry(message,
i18n(
"Invalid URL"));
535bool FITSTab::saveFileAs()
541void FITSTab::ZoomIn()
543 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center());
548void FITSTab::ZoomOut()
550 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center());
555void FITSTab::ZoomDefault()
557 QPoint oldCenter = m_View->getImagePoint(m_View->viewport()->rect().center());
558 m_View->ZoomDefault();
562void FITSTab::tabPositionUpdated()
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()),
570void FITSTab::setStretchValues(
double shadows,
double midtones,
double highlights)
573 stretchUI->setStretchValues(shadows, midtones, highlights);
583 case Ekos::AlignProfiles:
589 Ekos::getDefaultAlignOptionsProfiles();
591 case Ekos::FocusProfiles:
596 Ekos::getDefaultFocusOptionsProfiles();
598 case Ekos::GuideProfiles:
603 Ekos::getDefaultGuideOptionsProfiles();
605 case Ekos::HFRProfiles:
610 Ekos::getDefaultHFROptionsProfiles();
619 m_PlateSolveUI.kcfg_FitsSolverProfile->currentIndex());
620 parameters.search_radius = m_PlateSolveUI.kcfg_FitsSolverRadius->value();
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)
636 const double scale = m_PlateSolveUI.kcfg_FitsSolverScale->value();
641 const int units = m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits->currentIndex();
642 if (units == SSolver::DEG_WIDTH)
647 else if (units == SSolver::ARCMIN_WIDTH)
653 m_Solver->useScale(m_PlateSolveUI.kcfg_FitsSolverUseScale->isChecked(),
lowScale,
highScale);
655 else m_Solver->useScale(
false, 0, 0);
657 if (m_PlateSolveUI.kcfg_FitsSolverUsePosition->isChecked())
660 const dms ra = m_PlateSolveUI.FitsSolverEstRA->createDms(&ok);
662 const dms dec = m_PlateSolveUI.FitsSolverEstDec->createDms(&
ok2);
664 m_Solver->usePosition(
true, ra.
Degrees(),
dec.Degrees());
666 m_Solver->usePosition(
false, 0, 0);
668 else m_Solver->usePosition(
false, 0, 0);
673void FITSTab::extractImage()
675 if (m_Solver.
get() && m_Solver->isRunning())
677 m_PlateSolveUI.SolveButton->setText(
i18n(
"Aborting..."));
681 m_PlateSolveUI.SolveButton->setText(
i18n(
"Cancel"));
685 m_PlateSolveUI.FitsSolverAngle->setText(
"");
686 m_PlateSolveUI.Solution1->setText(
i18n(
"Extracting..."));
687 m_PlateSolveUI.Solution2->setText(
"");
689 m_Solver->runSolver(m_View->imageData());
692void FITSTab::solveImage()
694 if (m_Solver.
get() && m_Solver->isRunning())
696 m_PlateSolveUI.SolveButton->setText(
i18n(
"Aborting..."));
700 m_PlateSolveUI.SolveButton->setText(
i18n(
"Cancel"));
704 m_PlateSolveUI.Solution2->setText(
i18n(
"Solving..."));
706 m_Solver->runSolver(m_View->imageData());
709void FITSTab::extractorDone(
bool timedOut,
bool success,
const FITSImage::Solution &solution,
double elapsedSeconds)
712 disconnect(m_Solver.
get(), &SolverUtils::done,
this, &FITSTab::extractorDone);
713 m_PlateSolveUI.Solution2->setText(
"");
718 m_PlateSolveUI.Solution1->setText(result);
721 m_PlateSolveUI.SolveButton->setText(
"Solve");
727 m_PlateSolveUI.Solution1->setText(result);
730 m_PlateSolveUI.SolveButton->setText(
i18n(
"Solve"));
736 m_Solver->getNumStarsFound(),
737 m_Solver->getBackground().num_stars_detected,
739 m_PlateSolveUI.Solution1->setText(
starStr);
744 starCenters.reserve(
starList.size());
745 for (
int i = 0; i <
starList.size(); i++)
755 oneEdge->numPixels = star.numPixels;
759 oneEdge->ellipticity = 1 - star.b / star.a;
765 m_View->imageData()->setStarCenters(starCenters);
766 m_View->updateFrame();
773void FITSTab::solverDone(
bool timedOut,
bool success,
const FITSImage::Solution &solution,
double elapsedSeconds)
775 disconnect(m_Solver.
get(), &SolverUtils::done,
this, &FITSTab::solverDone);
776 m_PlateSolveUI.SolveButton->setText(
"Solve");
778 if (m_Solver->isRunning())
779 qCDebug(
KSTARS_FITS) <<
"solverDone called, but it is still running.";
784 m_PlateSolveUI.Solution2->setText(result);
789 m_PlateSolveUI.Solution2->setText(result);
794 m_View->imageData()->injectWCS(solution.orientation, solution.ra, solution.dec, solution.pixscale,
eastToTheRight);
795 m_View->imageData()->loadWCS();
798 const double solverPA = KSUtils::rotationToPositionAngle(solution.orientation);
799 m_PlateSolveUI.FitsSolverAngle->setText(
QString(
"%1ยบ").arg(
solverPA, 0,
'f', 2));
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);
809 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(solution.pixscale);
812 m_PlateSolveUI.FitsSolverEstRA->show(
dms(solution.ra));
813 m_PlateSolveUI.FitsSolverEstDec->show(
dms(solution.dec));
815 m_PlateSolveUI.Solution2->setText(result);
826 const QString str = Options::fitsSolverProfileIndeces();
838 QString str = Options::fitsSolverProfileIndeces();
843 for (
int i = 0; i < Ekos::ProfileGroupNames.size(); i++)
848 else if (name ==
"Guide")
850 else if (name ==
"HFR")
873 m_PlateSolveUI.kcfg_FitsSolverProfile->clear();
874 for(
auto &
param : optionsList)
880 m_PlateSolveUI.kcfg_FitsSolverProfile->setCurrentIndex(getProfileIndex(Options::fitsSolverModule()));
882 m_ProfileEditorPage->setHeader(
QString(
"FITS Viewer Solver %1 Profiles Editor")
886void FITSTab::initSolverUI()
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());
894 setupProfiles(Options::fitsSolverModule());
899 m_PlateSolveUI.kcfg_FitsSolverUseScale->setChecked(Options::fitsSolverUseScale());
900 m_PlateSolveUI.kcfg_FitsSolverScale->setValue(Options::fitsSolverScale());
901 m_PlateSolveUI.kcfg_FitsSolverImageScaleUnits->setCurrentIndex(Options::fitsSolverImageScaleUnits());
903 m_PlateSolveUI.kcfg_FitsSolverUsePosition->setChecked(Options::fitsSolverUsePosition());
904 m_PlateSolveUI.kcfg_FitsSolverRadius->setValue(Options::fitsSolverRadius());
906 m_PlateSolveUI.FitsSolverEstRA->setUnits(dmsBox::HOURS);
907 m_PlateSolveUI.FitsSolverEstDec->setUnits(dmsBox::DEGREES);
912 setProfileIndex(m_PlateSolveUI.kcfg_FitsSolverModule->currentIndex(), index);
917 Options::setFitsSolverUseScale(state);
921 Options::setFitsSolverScale(value);
925 Options::setFitsSolverImageScaleUnits(index);
930 Options::setFitsSolverUsePosition(state);
935 Options::setFitsSolverRadius(value);
939 const auto center = SkyMap::Instance()->getCenterPoint();
940 m_PlateSolveUI.FitsSolverEstRA->show(center.ra());
941 m_PlateSolveUI.FitsSolverEstDec->show(center.dec());
945 const SSolver::SolverType
type =
static_cast<SSolver::SolverType
>(Options::solverType());
946 if(type != SSolver::SOLVER_STELLARSOLVER)
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."));
Primary window to view monochrome and color FITS images.
static KStars * Instance()
An angle, stored as degrees, but expressible in many ways.
const double & Degrees() const
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.
QString name(StandardShortcut id)
void stateChanged(int state)
void setImage(const QImage &image, Mode mode)
void activated(int index)
QString filePath(const QString &fileName) const const
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
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
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
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
QTextStream & dec(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setActive(bool active)
bool isClean() const const
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