diff --git a/kstars/fitsviewer/fitstab.cpp b/kstars/fitsviewer/fitstab.cpp index f1f899feb..1709c0d8f 100644 --- a/kstars/fitsviewer/fitstab.cpp +++ b/kstars/fitsviewer/fitstab.cpp @@ -1,428 +1,463 @@ /*************************************************************************** FITS Tab ------------------- copyright : (C) 2012 by Jasem Mutlaq email : mutlaqja@ikarustech.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "fitstab.h" #include "fitsdata.h" #include "fitshistogram.h" #include "fitsview.h" #include "fitsviewer.h" #include "ksnotification.h" #include "kstars.h" #include "Options.h" #include "ui_fitsheaderdialog.h" #include "ui_statform.h" #include #include FITSTab::FITSTab(FITSViewer *parent) : QWidget(parent) { viewer = parent; undoStack = new QUndoStack(this); undoStack->setUndoLimit(10); undoStack->clear(); connect(undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(modifyFITSState(bool))); statWidget = new QDialog(this); fitsHeaderDialog = new QDialog(this); histogram = new FITSHistogram(this); } FITSTab::~FITSTab() { // Make sure it's done //histogramFuture.waitForFinished(); //disconnect(); } void FITSTab::saveUnsaved() { if (undoStack->isClean() || view->getMode() != FITS_NORMAL) return; QString caption = i18n("Save Changes to FITS?"); QString message = i18n("The current FITS file has unsaved changes. Would you like to save before closing it?"); int ans = KMessageBox::warningYesNoCancel(nullptr, message, caption, KStandardGuiItem::save(), KStandardGuiItem::discard()); if (ans == KMessageBox::Yes) saveFile(); if (ans == KMessageBox::No) { undoStack->clear(); modifyFITSState(); } } void FITSTab::closeEvent(QCloseEvent *ev) { saveUnsaved(); if (undoStack->isClean()) ev->accept(); else ev->ignore(); } QString FITSTab::getPreviewText() const { return previewText; } void FITSTab::setPreviewText(const QString &value) { previewText = value; } void FITSTab::selectRecentFITS(int i) { loadFITS(QUrl::fromLocalFile(recentImages->item(i)->text())); } void FITSTab::clearRecentFITS() { disconnect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); recentImages->clear(); connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); } void FITSTab::loadFITS(const QUrl &imageURL, FITSMode mode, FITSScale filter, bool silent) { if (view.get() == nullptr) { view.reset(new FITSView(this, mode, filter)); view->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QVBoxLayout *vlayout = new QVBoxLayout(); fitsSplitter = new QSplitter(Qt::Horizontal, this); fitsTools = new QToolBox(); stat.setupUi(statWidget); + + for (int i = 0; i <= STAT_STDDEV; i++) + { + for (int j = 0; j < 3; j++) + { + stat.statsTable->setItem(i, j, new QTableWidgetItem()); + stat.statsTable->item(i, j)->setTextAlignment(Qt::AlignHCenter); + } + + // Set col span for items up to HFR + if (i <= STAT_HFR) + stat.statsTable->setSpan(i, 0, 1, 3); + } + fitsTools->addItem(statWidget, i18n("Statistics")); fitsTools->addItem(histogram, i18n("Histogram")); header.setupUi(fitsHeaderDialog); fitsTools->addItem(fitsHeaderDialog, i18n("FITS Header")); QVBoxLayout *recentPanelLayout = new QVBoxLayout(); QWidget *recentPanel = new QWidget(fitsSplitter); recentPanel->setLayout(recentPanelLayout); fitsTools->addItem(recentPanel, i18n("Recent Images")); recentImages = new QListWidget(recentPanel); recentPanelLayout->addWidget(recentImages); QPushButton *clearRecent = new QPushButton(i18n("Clear")); recentPanelLayout->addWidget(clearRecent); connect(clearRecent, &QPushButton::pressed, this, &FITSTab::clearRecentFITS); connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); QScrollArea *scrollFitsPanel = new QScrollArea(fitsSplitter); scrollFitsPanel->setWidgetResizable(true); scrollFitsPanel->setWidget(fitsTools); fitsSplitter->addWidget(scrollFitsPanel); fitsSplitter->addWidget(view.get()); //This code allows the fitsTools to start in a closed state fitsSplitter->setSizes(QList() << 0 << view->width() ); vlayout->addWidget(fitsSplitter); connect(fitsSplitter, &QSplitter::splitterMoved, histogram, &FITSHistogram::resizePlot); setLayout(vlayout); connect(view.get(), &FITSView::newStatus, this, &FITSTab::newStatus); connect(view.get(), &FITSView::debayerToggled, this, &FITSTab::debayerToggled); // On Failure to load connect(view.get(), &FITSView::failed, this, &FITSTab::failed); // On Success loading image connect(view.get(), &FITSView::loaded, [&]() { // If it was already running make sure it's done //histogramFuture.waitForFinished(); FITSData *image_data = view->getImageData(); histogram->reset(); image_data->setHistogram(histogram); // Only construct histogram if it is actually visible // Otherwise wait until histogram is needed before creating it. if (fitsSplitter->sizes().at(0) != 0) { histogram->constructHistogram(); } evaluateStats(); // if (histogram == nullptr) // { // histogram = new FITSHistogram(this); // image_data->setHistogram(histogram); // } //histogramFuture = QtConcurrent::run([&]() {histogram->constructHistogram(); evaluateStats();}); //if(histogram->isVisible()) // histogramFuture.waitForFinished(); // if (filter != FITS_NONE) // { // image_data->applyFilter(filter); // view->rescale(ZOOM_KEEP_LEVEL); // } if (viewer->isStarsMarked()) view->toggleStars(true); loadFITSHeader(); if(recentImages->findItems(currentURL.toLocalFile(), Qt::MatchExactly).count() == 0) //Don't add it to the list if it is already there { if(!image_data->isTempFile()) //Don't add it to the list if it is a preview { disconnect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); recentImages->addItem(currentURL.toLocalFile()); recentImages->setCurrentRow(recentImages->count() - 1); connect(recentImages, &QListWidget::currentRowChanged, this, &FITSTab::selectRecentFITS); } } view->updateFrame(); emit loaded(); }); } currentURL = imageURL; view->setFilter(filter); view->loadFITS(imageURL.toLocalFile(), silent); } void FITSTab::modifyFITSState(bool clean) { if (clean) { if (undoStack->isClean() == false) undoStack->setClean(); mDirty = false; } else mDirty = true; emit changeStatus(clean); } int FITSTab::saveFITS(const QString &filename) { return view->saveFITS(filename); } void FITSTab::copyFITS() { QApplication::clipboard()->setImage(view->getDisplayImage()); } void FITSTab::histoFITS() { if (!histogram->isConstructed()) { histogram->constructHistogram(); evaluateStats(); } fitsTools->setCurrentIndex(1); if(view->width() > 200) fitsSplitter->setSizes(QList() << 200 << view->width() - 200); else fitsSplitter->setSizes(QList() << 50 << 50); } void FITSTab::evaluateStats() { FITSData *image_data = view->getImageData(); - stat.widthOUT->setText(QString::number(image_data->width())); - stat.heightOUT->setText(QString::number(image_data->height())); - stat.bitpixOUT->setText(QString::number(image_data->bpp())); - stat.maxOUT->setText(QString::number(image_data->getMax(), 'f', 3)); - stat.minOUT->setText(QString::number(image_data->getMin(), 'f', 3)); - stat.meanOUT->setText(QString::number(image_data->getMean(), 'f', 3)); - stat.stddevOUT->setText(QString::number(image_data->getStdDev(), 'f', 3)); - stat.HFROUT->setText(QString::number(image_data->getHFR(), 'f', 3)); + stat.statsTable->item(STAT_WIDTH, 0)->setText(QString::number(image_data->width())); + stat.statsTable->item(STAT_HEIGHT, 0)->setText(QString::number(image_data->height())); + stat.statsTable->item(STAT_BITPIX, 0)->setText(QString::number(image_data->bpp())); + stat.statsTable->item(STAT_HFR, 0)->setText(QString::number(image_data->getHFR(), 'f', 3)); + + if (image_data->channels() == 1) + { + for (int i = STAT_MIN; i <= STAT_STDDEV; i++) + stat.statsTable->setSpan(i, 0, 1, 3); + + stat.statsTable->horizontalHeaderItem(0)->setText(i18n("Value")); + stat.statsTable->hideColumn(1); + stat.statsTable->hideColumn(2); + } + else + { + for (int i = STAT_MIN; i <= STAT_STDDEV; i++) + stat.statsTable->setSpan(i, 0, 1, 1); + + stat.statsTable->horizontalHeaderItem(0)->setText(i18nc("Red", "R")); + stat.statsTable->showColumn(1); + stat.statsTable->showColumn(2); + } if (image_data->getMedian() == 0.0 && !histogram->isConstructed()) histogram->constructHistogram(); - stat.medianOUT->setText(QString::number(image_data->getMedian(), 'f', 3)); - stat.SNROUT->setText(QString::number(image_data->getSNR(), 'f', 3)); + for (int i = 0; i < image_data->channels(); i++) + { + stat.statsTable->item(STAT_MIN, i)->setText(QString::number(image_data->getMin(i), 'f', 3)); + stat.statsTable->item(STAT_MAX, i)->setText(QString::number(image_data->getMax(i), 'f', 3)); + stat.statsTable->item(STAT_MEAN, i)->setText(QString::number(image_data->getMean(i), 'f', 3)); + stat.statsTable->item(STAT_MEDIAN, i)->setText(QString::number(image_data->getMedian(i), 'f', 3)); + stat.statsTable->item(STAT_STDDEV, i)->setText(QString::number(image_data->getStdDev(i), 'f', 3)); + } } void FITSTab::statFITS() { fitsTools->setCurrentIndex(0); if(view->width() > 200) fitsSplitter->setSizes(QList() << 200 << view->width() - 200); else fitsSplitter->setSizes(QList() << 50 << 50); } void FITSTab::loadFITSHeader() { FITSData *image_data = view->getImageData(); int nkeys = image_data->getRecords().size(); int counter = 0; header.tableWidget->setRowCount(nkeys); for (FITSData::Record *oneRecord : image_data->getRecords()) { QTableWidgetItem *tempItem = new QTableWidgetItem(oneRecord->key); tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); header.tableWidget->setItem(counter, 0, tempItem); tempItem = new QTableWidgetItem(oneRecord->value.toString()); tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); header.tableWidget->setItem(counter, 1, tempItem); tempItem = new QTableWidgetItem(oneRecord->comment); tempItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); header.tableWidget->setItem(counter, 2, tempItem); counter++; } header.tableWidget->setColumnWidth(0, 100); header.tableWidget->setColumnWidth(1, 100); header.tableWidget->setColumnWidth(2, 250); } void FITSTab::headerFITS() { fitsTools->setCurrentIndex(2); if(view->width() > 200) fitsSplitter->setSizes(QList() << 200 << view->width() - 200); else fitsSplitter->setSizes(QList() << 50 << 50); } bool FITSTab::saveFile() { QUrl backupCurrent = currentURL; QUrl currentDir(Options::fitsDir()); currentDir.setScheme("file"); if (currentURL.toLocalFile().startsWith(QLatin1String("/tmp/")) || currentURL.toLocalFile().contains("/Temp")) currentURL.clear(); // If no changes made, return. if (mDirty == false && !currentURL.isEmpty()) return false; if (currentURL.isEmpty()) { currentURL = QFileDialog::getSaveFileUrl(KStars::Instance(), i18n("Save FITS"), currentDir, "FITS (*.fits *.fits.gz *.fit)"); // if user presses cancel if (currentURL.isEmpty()) { currentURL = backupCurrent; return false; } if (currentURL.toLocalFile().contains('.') == 0) currentURL.setPath(currentURL.toLocalFile() + ".fits"); // Already display by dialog /*if (QFile::exists(currentURL.toLocalFile())) { int r = KMessageBox::warningContinueCancel(0, i18n( "A file named \"%1\" already exists. " "Overwrite it?", currentURL.fileName() ), i18n( "Overwrite File?" ), KGuiItem(i18n( "&Overwrite" )) ); if(r==KMessageBox::Cancel) return false; }*/ } if (currentURL.isValid()) { int err_status = 0; if ((err_status = saveFITS('!' + currentURL.toLocalFile())) != 0) { // -1000 = user canceled if (err_status == -1000) return false; char err_text[FLEN_STATUS]; fits_get_errstatus(err_status, err_text); // Use KMessageBox or something here KSNotification::error(i18n("FITS file save error: %1", QString::fromUtf8(err_text)), i18n("FITS Save")); return false; } //statusBar()->changeItem(i18n("File saved."), 3); emit newStatus(i18n("File saved to %1", currentURL.url()), FITS_MESSAGE); modifyFITSState(); return true; } else { QString message = i18n("Invalid URL: %1", currentURL.url()); KSNotification::sorry(message, i18n("Invalid URL")); return false; } } bool FITSTab::saveFileAs() { currentURL.clear(); return saveFile(); } void FITSTab::ZoomIn() { QPoint oldCenter = view->getImagePoint(view->viewport()->rect().center()); view->ZoomIn(); view->cleanUpZoom(oldCenter); } void FITSTab::ZoomOut() { QPoint oldCenter = view->getImagePoint(view->viewport()->rect().center()); view->ZoomOut(); view->cleanUpZoom(oldCenter); } void FITSTab::ZoomDefault() { QPoint oldCenter = view->getImagePoint(view->viewport()->rect().center()); view->ZoomDefault(); view->cleanUpZoom(oldCenter); } void FITSTab::tabPositionUpdated() { undoStack->setActive(true); emit newStatus(QString("%1%").arg(view->getCurrentZoom()), FITS_ZOOM); emit newStatus(QString("%1x%2").arg(view->getImageData()->width()).arg(view->getImageData()->height()), FITS_RESOLUTION); } diff --git a/kstars/fitsviewer/fitstab.h b/kstars/fitsviewer/fitstab.h index f82d3d0bf..44216ad3a 100644 --- a/kstars/fitsviewer/fitstab.h +++ b/kstars/fitsviewer/fitstab.h @@ -1,151 +1,164 @@ /*************************************************************************** FITS Tab ------------------- copyright : (C) 2012 by Jasem Mutlaq email : mutlaqja@ikarustech.com ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #pragma once #include "fitscommon.h" #include #include #include #include #include #include "ui_fitsheaderdialog.h" #include "ui_statform.h" #include #include #include #include class FITSHistogram; class FITSView; class FITSViewer; /** * @brief The FITSTab class holds information on the current view (drawing area) in addition to the undo/redo stacks * and status of current document (clean or dirty). It also creates the corresponding histogram associated with the * image data that is stored in the FITSView class. * @author Jasem Mutlaq */ class FITSTab : public QWidget { Q_OBJECT public: explicit FITSTab(FITSViewer *parent); - virtual ~FITSTab(); + virtual ~FITSTab() override; + + enum + { + STAT_WIDTH, + STAT_HEIGHT, + STAT_BITPIX, + STAT_HFR, + STAT_MIN, + STAT_MAX, + STAT_MEAN, + STAT_MEDIAN, + STAT_STDDEV + }; void clearRecentFITS(); void selectRecentFITS(int i); void loadFITS(const QUrl &imageURL, FITSMode mode = FITS_NORMAL, FITSScale filter = FITS_NONE, bool silent = true); int saveFITS(const QString &filename); inline QUndoStack *getUndoStack() { return undoStack; } inline QUrl *getCurrentURL() { return ¤tURL; } inline FITSView *getView() { return view.get(); } inline QPointer getHistogram() { return histogram; } inline QPointer getViewer() { return viewer; } bool saveFile(); bool saveFileAs(); void copyFITS(); void loadFITSHeader(); void headerFITS(); void histoFITS(); void evaluateStats(); void statFITS(); void setUID(int newID) { uid = newID; } int getUID() { return uid; } void saveUnsaved(); void tabPositionUpdated(); void selectGuideStar(); QString getPreviewText() const; void setPreviewText(const QString &value); public slots: void modifyFITSState(bool clean = true); void ZoomIn(); void ZoomOut(); void ZoomDefault(); protected: virtual void closeEvent(QCloseEvent *ev) override; private: /** Ask user whether he wants to save changes and save if he do. */ /// The FITSTools Toolbox QPointer fitsTools; /// The Splitter for th FITSTools Toolbox QPointer fitsSplitter; /// The FITS Header Panel QPointer fitsHeaderDialog; Ui::fitsHeaderDialog header; /// The Statistics Panel QPointer statWidget; Ui::statForm stat; /// FITS Histogram QPointer histogram; QPointer viewer; QPointer recentImages; /// FITS image object std::unique_ptr view; /// History for undo/redo QUndoStack *undoStack { nullptr }; /// FITS File name and path QUrl currentURL; bool mDirty { false }; QString previewText; int uid { 0 }; //QFuture histogramFuture; signals: void debayerToggled(bool); void newStatus(const QString &msg, FITSBar id); void changeStatus(bool clean); void loaded(); void failed(); }; diff --git a/kstars/fitsviewer/statform.ui b/kstars/fitsviewer/statform.ui index e5982ce13..62f2141ef 100644 --- a/kstars/fitsviewer/statform.ui +++ b/kstars/fitsviewer/statform.ui @@ -1,210 +1,143 @@ statForm 0 0 - 172 - 390 + 211 + 323 Statistics - + + + 3 + - 4 + 3 - 4 + 3 - 4 + 3 - 4 - - - -1 + 3 - - - - Standard Deviation - - - Std. dev: - - - - - - - Height: - - - - - - - Bitpix: - - - - - - - Width: - - - - - - - true - - - - - - - true - - - - - - - true - - - - - - - - - - true - - - - - - - true - - - - - - - true - - - - - - - true - - - - - - + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + false + + + 50 + + true - - - - - - + + + false + + true - - - - - - - true - - - - - - - Min: - - - - - - - Mean: - - - - - - - Max: - - - - - - - Median - - - - - - - Half-Flux Radius - - - HFR: - - - - - - - Signal to Noise Ratio - - - SNR: - - - - + + + + Width + + + + + Height + + + + + Bitpix + + + + + HFR + + + + + Min + + + + + Max + + + + + Mean + + + + + Median + + + + + Std. Dev + + + + + R + + + + + G + + + + + B + + + + + Qt::Vertical 20 40 - - widthOUT - heightOUT - bitpixOUT - maxOUT - minOUT - meanOUT - stddevOUT -