diff --git a/kstars/fitsviewer/fitsheaderdialog.ui b/kstars/fitsviewer/fitsheaderdialog.ui index ddd88770c..e3374093b 100644 --- a/kstars/fitsviewer/fitsheaderdialog.ui +++ b/kstars/fitsviewer/fitsheaderdialog.ui @@ -1,91 +1,67 @@ fitsHeaderDialog 0 0 511 247 FITS Header true 6 9 9 9 9 QAbstractItemView::NoEditTriggers true true Keyword Value Comment - - - - QDialogButtonBox::Close - - - - - - buttonBox - rejected() - fitsHeaderDialog - reject() - - - 466 - 233 - - - 436 - 243 - - - - + diff --git a/kstars/fitsviewer/fitshistogram.cpp b/kstars/fitsviewer/fitshistogram.cpp index 9a1fc66d9..8e7d1de49 100644 --- a/kstars/fitsviewer/fitshistogram.cpp +++ b/kstars/fitsviewer/fitshistogram.cpp @@ -1,799 +1,811 @@ /* FITS Histogram Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) This application 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 "fitshistogram.h" #include "fits_debug.h" #include "fitsdata.h" #include "fitstab.h" #include "fitsview.h" #include "fitsviewer.h" #include "Options.h" #include #include histogramUI::histogramUI(QDialog *parent) : QDialog(parent) { setupUi(parent); setModal(false); } FITSHistogram::FITSHistogram(QWidget *parent) : QDialog(parent) { ui = new histogramUI(this); tab = dynamic_cast(parent); customPlot = ui->histogramPlot; customPlot->setBackground(QBrush(Qt::black)); customPlot->xAxis->setBasePen(QPen(Qt::white, 1)); customPlot->yAxis->setBasePen(QPen(Qt::white, 1)); customPlot->xAxis->setTickPen(QPen(Qt::white, 1)); customPlot->yAxis->setTickPen(QPen(Qt::white, 1)); customPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); customPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); customPlot->xAxis->setTickLabelColor(Qt::white); customPlot->yAxis->setTickLabelColor(Qt::white); customPlot->xAxis->setLabelColor(Qt::white); customPlot->yAxis->setLabelColor(Qt::white); customPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); customPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); customPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); customPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); customPlot->xAxis->grid()->setZeroLinePen(Qt::NoPen); customPlot->yAxis->grid()->setZeroLinePen(Qt::NoPen); r_graph = customPlot->addGraph(); r_graph->setBrush(QBrush(QColor(170, 40, 80))); r_graph->setPen(QPen(Qt::red)); //r_graph->setLineStyle(QCPGraph::lsImpulse); connect(ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(applyScale())); connect(customPlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(updateValues(QMouseEvent*))); connect(ui->minEdit, SIGNAL(valueChanged(double)), this, SLOT(updateLimits(double))); connect(ui->maxEdit, SIGNAL(valueChanged(double)), this, SLOT(updateLimits(double))); connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(checkRangeLimit(QCPRange))); } void FITSHistogram::showEvent(QShowEvent *event) { Q_UNUSED(event) syncGUI(); } void FITSHistogram::constructHistogram() { FITSData *image_data = tab->getView()->getImageData(); isGUISynced = false; switch (image_data->property("dataType").toInt()) { case TBYTE: constructHistogram(); break; case TSHORT: constructHistogram(); break; case TUSHORT: constructHistogram(); break; case TLONG: constructHistogram(); break; case TULONG: constructHistogram(); break; case TFLOAT: constructHistogram(); break; case TLONGLONG: constructHistogram(); break; case TDOUBLE: constructHistogram(); break; default: break; } if (isVisible()) syncGUI(); } template void FITSHistogram::constructHistogram() { FITSData *image_data = tab->getView()->getImageData(); uint16_t fits_w = image_data->width(), fits_h = image_data->height(); auto *buffer = reinterpret_cast(image_data->getImageBuffer()); image_data->getMinMax(&fits_min, &fits_max); uint32_t samples = fits_w * fits_h; binCount = sqrt(samples); intensity.fill(0, binCount); r_frequency.fill(0, binCount); cumulativeFrequency.fill(0, binCount); double pixel_range = fits_max - fits_min; binWidth = pixel_range / (binCount - 1); qCDebug(KSTARS_FITS) << "Histogram min:" << fits_min << ", max:" << fits_max << ", range:" << pixel_range << ", binW:" << binWidth << ", bin#:" << binCount; for (int i = 0; i < binCount; i++) intensity[i] = fits_min + (binWidth * i); uint16_t r_id = 0; if (image_data->channels() == 1) { for (uint32_t i = 0; i < samples; i += 4) { r_id = round((buffer[i] - fits_min) / binWidth); r_frequency[r_id >= binCount ? binCount - 1 : r_id] += 4; } } else { g_frequency.fill(0, binCount); b_frequency.fill(0, binCount); int g_offset = samples; int b_offset = samples * 2; for (uint32_t i = 0; i < samples; i += 4) { uint16_t g_id = 0, b_id = 0; r_id = round((buffer[i] - fits_min) / binWidth); r_frequency[r_id >= binCount ? binCount - 1 : r_id] += 4; g_id = round((buffer[i + g_offset] - fits_min) / binWidth); g_frequency[g_id >= binCount ? binCount - 1 : g_id] += 4; b_id = round((buffer[i + b_offset] - fits_min) / binWidth); b_frequency[b_id >= binCount ? binCount - 1 : b_id] += 4; } } // Cumulative Frequency int j = 0; double val = 0; for (int i = 0; i < binCount; i++) { val += r_frequency[j++]; cumulativeFrequency.replace(i, val); } if (image_data->channels() == 1) { for (int i = 0; i < binCount; i++) { if (r_frequency[i] > maxFrequency) maxFrequency = r_frequency[i]; } } else { for (int i = 0; i < binCount; i++) { if (r_frequency[i] > maxFrequency) maxFrequency = r_frequency[i]; if (g_frequency[i] > maxFrequency) maxFrequency = g_frequency[i]; if (b_frequency[i] > maxFrequency) maxFrequency = b_frequency[i]; } } double median = 0; int halfCumulative = cumulativeFrequency[binCount - 1] / 2; for (int i = 0; i < binCount; i++) { if (cumulativeFrequency[i] >= halfCumulative) { median = i * binWidth + fits_min; break; } } // Custom index to indicate the overall contrast of the image JMIndex = cumulativeFrequency[binCount / 8] / cumulativeFrequency[binCount / 4]; qCDebug(KSTARS_FITS) << "FITHistogram: JMIndex " << JMIndex; image_data->setMedian(median); } void FITSHistogram::syncGUI() { if (isGUISynced) return; FITSData *image_data = tab->getView()->getImageData(); ui->meanEdit->setText(QString::number(image_data->getMean())); ui->medianEdit->setText(QString::number(image_data->getMedian())); ui->minEdit->setMinimum(fits_min); ui->minEdit->setMaximum(fits_max - 1); ui->minEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); ui->minEdit->setValue(fits_min); ui->maxEdit->setMinimum(fits_min + 1); ui->maxEdit->setMaximum(fits_max); ui->maxEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); ui->maxEdit->setValue(fits_max); r_graph->setData(intensity, r_frequency); if (image_data->channels() > 1) { g_graph = customPlot->addGraph(); b_graph = customPlot->addGraph(); g_graph->setBrush(QBrush(QColor(40, 170, 80))); b_graph->setBrush(QBrush(QColor(80, 40, 170))); g_graph->setPen(QPen(Qt::green)); b_graph->setPen(QPen(Qt::blue)); g_graph->setData(intensity, g_frequency); b_graph->setData(intensity, b_frequency); } customPlot->axisRect(0)->setRangeDrag(Qt::Horizontal); customPlot->axisRect(0)->setRangeZoom(Qt::Horizontal); customPlot->xAxis->setLabel(i18n("Intensity")); customPlot->yAxis->setLabel(i18n("Frequency")); customPlot->xAxis->setRange(fits_min, fits_max); if (maxFrequency > 0) - customPlot->yAxis->setRange(0, maxFrequency); + customPlot->yAxis->rescale(); customPlot->setInteraction(QCP::iRangeDrag, true); customPlot->setInteraction(QCP::iRangeZoom, true); customPlot->setInteraction(QCP::iSelectPlottables, true); customPlot->replot(); + resizePlot(); isGUISynced = true; } + +void FITSHistogram::resizePlot() +{ + if(customPlot->width()<300) + customPlot->yAxis->setTickLabels(false); + else + customPlot->yAxis->setTickLabels(true); + customPlot->xAxis->ticker()->setTickCount(customPlot->width()/100); + +} + #if 0 template void FITSHistogram::constructHistogram() { uint16_t fits_w = 0, fits_h = 0; FITSData *image_data = tab->getView()->getImageData(); T *buffer = reinterpret_cast(image_data->getImageBuffer()); image_data->getDimensions(&fits_w, &fits_h); image_data->getMinMax(&fits_min, &fits_max); uint32_t samples = fits_w * fits_h; binCount = sqrt(samples); intensity.fill(0, binCount); r_frequency.fill(0, binCount); cumulativeFrequency.fill(0, binCount); double pixel_range = fits_max - fits_min; binWidth = pixel_range / (binCount - 1); qCDebug(KSTARS_FITS) << "Histogram min:" << fits_min << ", max:" << fits_max << ", range:" << pixel_range << ", binW:" << binWidth << ", bin#:" << binCount; for (int i = 0; i < binCount; i++) intensity[i] = fits_min + (binWidth * i); uint16_t r_id = 0; if (image_data->getNumOfChannels() == 1) { for (uint32_t i = 0; i < samples; i += 4) { r_id = round((buffer[i] - fits_min) / binWidth); r_frequency[r_id >= binCount ? binCount - 1 : r_id] += 4; } } else { g_frequency.fill(0, binCount); b_frequency.fill(0, binCount); int g_offset = samples; int b_offset = samples * 2; for (uint32_t i = 0; i < samples; i += 4) { uint16_t g_id = 0, b_id = 0; r_id = round((buffer[i] - fits_min) / binWidth); r_frequency[r_id >= binCount ? binCount - 1 : r_id] += 4; g_id = round((buffer[i + g_offset] - fits_min) / binWidth); g_frequency[g_id >= binCount ? binCount - 1 : g_id] += 4; b_id = round((buffer[i + b_offset] - fits_min) / binWidth); b_frequency[b_id >= binCount ? binCount - 1 : b_id] += 4; } } // Cumulative Frequency for (int i = 0; i < binCount; i++) for (int j = 0; j <= i; j++) cumulativeFrequency[i] += r_frequency[j]; int maxFrequency = 0; if (image_data->getNumOfChannels() == 1) { for (int i = 0; i < binCount; i++) { if (r_frequency[i] > maxFrequency) maxFrequency = r_frequency[i]; } } else { for (int i = 0; i < binCount; i++) { if (r_frequency[i] > maxFrequency) maxFrequency = r_frequency[i]; if (g_frequency[i] > maxFrequency) maxFrequency = g_frequency[i]; if (b_frequency[i] > maxFrequency) maxFrequency = b_frequency[i]; } } double median = 0; int halfCumulative = cumulativeFrequency[binCount - 1] / 2; for (int i = 0; i < binCount; i++) { if (cumulativeFrequency[i] >= halfCumulative) { median = i * binWidth + fits_min; break; } } // Custom index to indicate the overall constrast of the image JMIndex = cumulativeFrequency[binCount / 8] / cumulativeFrequency[binCount / 4]; qCDebug(KSTARS_FITS) << "FITHistogram: JMIndex " << JMIndex; image_data->setMedian(median); ui->meanEdit->setText(QString::number(image_data->getMean())); ui->medianEdit->setText(QString::number(median)); ui->minEdit->setMinimum(fits_min); ui->minEdit->setMaximum(fits_max - 1); ui->minEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); ui->minEdit->setValue(fits_min); ui->maxEdit->setMinimum(fits_min + 1); ui->maxEdit->setMaximum(fits_max); ui->maxEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); ui->maxEdit->setValue(fits_max); r_graph->setData(intensity, r_frequency); if (image_data->getNumOfChannels() > 1) { g_graph = customPlot->addGraph(); b_graph = customPlot->addGraph(); g_graph->setBrush(QBrush(QColor(40, 170, 80))); b_graph->setBrush(QBrush(QColor(80, 40, 170))); g_graph->setPen(QPen(Qt::green)); b_graph->setPen(QPen(Qt::blue)); g_graph->setData(intensity, g_frequency); b_graph->setData(intensity, b_frequency); } customPlot->axisRect(0)->setRangeDrag(Qt::Horizontal); customPlot->axisRect(0)->setRangeZoom(Qt::Horizontal); customPlot->xAxis->setLabel(i18n("Intensity")); customPlot->yAxis->setLabel(i18n("Frequency")); customPlot->xAxis->setRange(fits_min, fits_max); if (maxFrequency > 0) customPlot->yAxis->setRange(0, maxFrequency); customPlot->setInteraction(QCP::iRangeDrag, true); customPlot->setInteraction(QCP::iRangeZoom, true); customPlot->setInteraction(QCP::iSelectPlottables, true); customPlot->replot(); } #endif void FITSHistogram::updateLimits(double value) { if (sender() == ui->minEdit) { if (value > ui->maxEdit->value()) ui->maxEdit->setValue(value + 1); } else if (sender() == ui->maxEdit) { if (value < ui->minEdit->value()) { ui->minEdit->setValue(value); ui->maxEdit->setValue(value + 1); } } } void FITSHistogram::checkRangeLimit(const QCPRange &range) { if (range.lower < fits_min) customPlot->xAxis->setRangeLower(fits_min); else if (range.upper > fits_max) customPlot->xAxis->setRangeUpper(fits_max); } double FITSHistogram::getJMIndex() const { return JMIndex; } void FITSHistogram::applyScale() { double min = ui->minEdit->value(); double max = ui->maxEdit->value(); FITSHistogramCommand *histC; if (ui->logR->isChecked()) type = FITS_LOG; else type = FITS_LINEAR; histC = new FITSHistogramCommand(tab, this, type, min, max); tab->getUndoStack()->push(histC); } void FITSHistogram::applyFilter(FITSScale ftype) { double min = ui->minEdit->value(); double max = ui->maxEdit->value(); FITSHistogramCommand *histC; type = ftype; histC = new FITSHistogramCommand(tab, this, type, min, max); tab->getUndoStack()->push(histC); } QVector FITSHistogram::getCumulativeFrequency() const { return cumulativeFrequency; } void FITSHistogram::updateValues(QMouseEvent *event) { int x = event->x(); double intensity_key = customPlot->xAxis->pixelToCoord(x); if (intensity_key < 0) return; double frequency_val = 0; for (int i = 0; i < binCount; i++) { if (intensity[i] > intensity_key) { frequency_val = r_frequency[i]; break; } } ui->intensityEdit->setText(QString::number(intensity_key)); ui->frequencyEdit->setText(QString::number(frequency_val)); } FITSHistogramCommand::FITSHistogramCommand(QWidget *parent, FITSHistogram *inHisto, FITSScale newType, double lmin, double lmax) { tab = dynamic_cast(parent); type = newType; histogram = inHisto; min = lmin; max = lmax; } FITSHistogramCommand::~FITSHistogramCommand() { delete[] delta; } bool FITSHistogramCommand::calculateDelta(const uint8_t *buffer) { FITSData *image_data = tab->getView()->getImageData(); uint8_t *image_buffer = image_data->getImageBuffer(); int totalPixels = image_data->width() * image_data->height() * image_data->channels(); unsigned long totalBytes = totalPixels * image_data->getBytesPerPixel(); auto *raw_delta = new uint8_t[totalBytes]; if (raw_delta == nullptr) { qWarning() << "Error! not enough memory to create image delta" << endl; return false; } for (unsigned int i = 0; i < totalBytes; i++) raw_delta[i] = buffer[i] ^ image_buffer[i]; compressedBytes = sizeof(uint8_t) * totalBytes + totalBytes / 64 + 16 + 3; delete[] delta; delta = new uint8_t[compressedBytes]; if (delta == nullptr) { delete[] raw_delta; qCCritical(KSTARS_FITS) << "FITSHistogram Error: Ran out of memory compressing delta"; return false; } int r = compress2(delta, &compressedBytes, raw_delta, totalBytes, 5); if (r != Z_OK) { delete[] raw_delta; /* this should NEVER happen */ qCCritical(KSTARS_FITS) << "FITSHistogram Error: Failed to compress raw_delta"; return false; } //qDebug() << "compressed bytes size " << compressedBytes << " bytes" << endl; delete[] raw_delta; return true; } bool FITSHistogramCommand::reverseDelta() { FITSView *image = tab->getView(); FITSData *image_data = image->getImageData(); uint8_t *image_buffer = (image_data->getImageBuffer()); int totalPixels = image_data->width() * image_data->height() * image_data->channels(); unsigned long totalBytes = totalPixels * image_data->getBytesPerPixel(); auto *output_image = new uint8_t[totalBytes]; if (output_image == nullptr) { qWarning() << "Error! not enough memory to create output image" << endl; return false; } auto *raw_delta = new uint8_t[totalBytes]; if (raw_delta == nullptr) { delete[] output_image; qWarning() << "Error! not enough memory to create image delta" << endl; return false; } int r = uncompress(raw_delta, &totalBytes, delta, compressedBytes); if (r != Z_OK) { qCCritical(KSTARS_FITS) << "FITSHistogram compression error in reverseDelta()"; delete[] output_image; delete[] raw_delta; return false; } for (unsigned int i = 0; i < totalBytes; i++) output_image[i] = raw_delta[i] ^ image_buffer[i]; image_data->setImageBuffer(output_image); delete[] raw_delta; return true; } void FITSHistogramCommand::redo() { FITSView *image = tab->getView(); FITSData *image_data = image->getImageData(); uint8_t *image_buffer = image_data->getImageBuffer(); uint8_t *buffer = nullptr; unsigned int size = image_data->width() * image_data->height() * image_data->channels(); int BBP = image_data->getBytesPerPixel(); QApplication::setOverrideCursor(Qt::WaitCursor); if (delta != nullptr) { FITSData::Statistic prevStats; image_data->saveStatistics(prevStats); reverseDelta(); image_data->restoreStatistics(stats); stats = prevStats; } else { image_data->saveStatistics(stats); // If it's rotation of flip, no need to calculate delta if (type >= FITS_ROTATE_CW && type <= FITS_FLIP_V) { image_data->applyFilter(type, image_buffer); } else { buffer = new uint8_t[size * BBP]; if (buffer == nullptr) { qWarning() << "Error! not enough memory to create image buffer in redo()" << endl; QApplication::restoreOverrideCursor(); return; } memcpy(buffer, image_buffer, size * BBP); double dataMin = min, dataMax = max; switch (type) { case FITS_AUTO: case FITS_LINEAR: image_data->applyFilter(FITS_LINEAR, nullptr, &dataMin, &dataMax); break; case FITS_LOG: image_data->applyFilter(FITS_LOG, nullptr, &dataMin, &dataMax); break; case FITS_SQRT: image_data->applyFilter(FITS_SQRT, nullptr, &dataMin, &dataMax); break; default: image_data->applyFilter(type); break; } calculateDelta(buffer); delete[] buffer; } } if (histogram != nullptr) { histogram->constructHistogram(); if (tab->getViewer()->isStarsMarked()) image_data->findStars(); } image->pushFilter(type); image->rescale(ZOOM_KEEP_LEVEL); image->updateFrame(); QApplication::restoreOverrideCursor(); } void FITSHistogramCommand::undo() { FITSView *image = tab->getView(); FITSData *image_data = image->getImageData(); QApplication::setOverrideCursor(Qt::WaitCursor); if (delta != nullptr) { FITSData::Statistic prevStats; image_data->saveStatistics(prevStats); reverseDelta(); image_data->restoreStatistics(stats); stats = prevStats; } else { switch (type) { case FITS_ROTATE_CW: image_data->applyFilter(FITS_ROTATE_CCW); break; case FITS_ROTATE_CCW: image_data->applyFilter(FITS_ROTATE_CW); break; case FITS_FLIP_H: case FITS_FLIP_V: image_data->applyFilter(type); break; default: break; } } if (histogram != nullptr) { histogram->constructHistogram(); if (tab->getViewer()->isStarsMarked()) image_data->findStars(); } image->popFilter(); image->rescale(ZOOM_KEEP_LEVEL); image->updateFrame(); QApplication::restoreOverrideCursor(); } QString FITSHistogramCommand::text() const { switch (type) { case FITS_AUTO: return i18n("Auto Scale"); break; case FITS_LINEAR: return i18n("Linear Scale"); break; case FITS_LOG: return i18n("Logarithmic Scale"); break; case FITS_SQRT: return i18n("Square Root Scale"); break; default: if (type - 1 <= FITSViewer::filterTypes.count()) return FITSViewer::filterTypes.at(type - 1); break; } return i18n("Unknown"); } diff --git a/kstars/fitsviewer/fitshistogram.h b/kstars/fitsviewer/fitshistogram.h index e4791d2bb..7b168db98 100644 --- a/kstars/fitsviewer/fitshistogram.h +++ b/kstars/fitsviewer/fitshistogram.h @@ -1,110 +1,111 @@ /* FITS Histogram Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) This application 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 "fitsdata.h" #include "ui_fitshistogramui.h" #include #include class QMouseEvent; class FITSTab; class histogramUI : public QDialog, public Ui::FITSHistogramUI { Q_OBJECT public: explicit histogramUI(QDialog *parent = nullptr); }; class FITSHistogram : public QDialog { Q_OBJECT friend class histDrawArea; public: explicit FITSHistogram(QWidget *parent); ~FITSHistogram() = default; void constructHistogram(); void syncGUI(); void applyFilter(FITSScale ftype); double getBinWidth() { return binWidth; } QVector getCumulativeFrequency() const; double getJMIndex() const; protected: void showEvent(QShowEvent *event); public slots: void applyScale(); void updateValues(QMouseEvent *event); void updateLimits(double value); void checkRangeLimit(const QCPRange &range); + void resizePlot(); private: template void constructHistogram(); histogramUI *ui { nullptr }; FITSTab *tab { nullptr }; QVector intensity; QVector r_frequency, g_frequency, b_frequency; QCPGraph *r_graph { nullptr }; QCPGraph *g_graph { nullptr }; QCPGraph *b_graph { nullptr }; QVector cumulativeFrequency; double binWidth { 0 }; double JMIndex { 0 }; double fits_min { 0 }; double fits_max { 0 }; uint16_t binCount { 0 }; int maxFrequency {0}; FITSScale type { FITS_AUTO }; bool isGUISynced { false}; QCustomPlot *customPlot { nullptr }; }; class FITSHistogramCommand : public QUndoCommand { public: FITSHistogramCommand(QWidget *parent, FITSHistogram *inHisto, FITSScale newType, double lmin, double lmax); virtual ~FITSHistogramCommand(); virtual void redo(); virtual void undo(); virtual QString text() const; private: bool calculateDelta(const uint8_t *buffer); bool reverseDelta(); FITSData::Statistic stats; FITSHistogram *histogram { nullptr }; FITSScale type; double min { 0 }; double max { 0 }; unsigned char *delta { nullptr }; unsigned long compressedBytes { 0 }; FITSTab *tab { nullptr }; }; diff --git a/kstars/fitsviewer/fitshistogramui.ui b/kstars/fitsviewer/fitshistogramui.ui index baf8f58b0..c0b8d5bf0 100644 --- a/kstars/fitsviewer/fitshistogramui.ui +++ b/kstars/fitsviewer/fitshistogramui.ui @@ -1,255 +1,237 @@ FITSHistogramUI 0 0 - 640 - 360 + 212 + 528 0 0 0 0 16777215 16777215 Histogram true + + 4 + + + 4 + + + 4 + + + 4 + 0 0 - 500 + 0 200 16777215 16777215 - + + + + + + + Mean: + + + + + + + Intensity: + + + + + + + - Min. + Min.: - - + + - Max. + Frequency: - - + + 0 0 true - - + + 0 0 true - - + + + + Max: + + + + + 0 0 true - + - Median + Median: - + 0 0 true - - - - Intensity: - - - - - - - Frequency: - - - - - - - Mean - - - - - - - - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - false - - - FITS Scale - - - - - - L&inear - - - true - - - - - - - &Logarithmic - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - + + + + + 0 + 0 + + + + Qt::LeftToRight + + + false + + + FITS Scale + + + + + + L&inear + + + true + + + + + + + &Log + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Close QCustomPlot QWidget
auxiliary/qcustomplot.h
1
- - - buttonBox - rejected() - FITSHistogramUI - reject() - - - 589 - 336 - - - 579 - 357 - - - - +
diff --git a/kstars/fitsviewer/fitstab.cpp b/kstars/fitsviewer/fitstab.cpp index c339e24e2..0d79a0815 100644 --- a/kstars/fitsviewer/fitstab.cpp +++ b/kstars/fitsviewer/fitstab.cpp @@ -1,324 +1,372 @@ /*************************************************************************** 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 "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::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(); - vlayout->addWidget(view.get()); + fitsSplitter = new QSplitter(Qt::Horizontal,this); + fitsTools = new QToolBox(); + + stat.setupUi(statWidget); + fitsTools->addItem(statWidget,i18n("Statistics")); + + fitsTools->addItem(histogram,i18n("Histogram")); + + + header.setupUi(fitsHeaderDialog); + fitsTools->addItem(fitsHeaderDialog,i18n("FITS Header")); + + 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, [&,filter]() { // If it was already running make sure it's done histogramFuture.waitForFinished(); FITSData *image_data = view->getImageData(); if (histogram == nullptr) { histogram = new FITSHistogram(this); image_data->setHistogram(histogram); } histogramFuture = QtConcurrent::run([&]() {histogram->constructHistogram();}); // if (filter != FITS_NONE) // { // image_data->applyFilter(filter); // view->rescale(ZOOM_KEEP_LEVEL); // } if (viewer->isStarsMarked()) view->toggleStars(true); + evaluateStats(); + loadFITSHeader(); + 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() { - histogram->show(); + fitsTools->setCurrentIndex(1); + if(view->width()>200) + fitsSplitter->setSizes(QList() << 200 << view->width() - 200); + else + fitsSplitter->setSizes(QList() << 50 << 50); } -void FITSTab::statFITS() +void FITSTab::evaluateStats() { - QDialog statDialog; - Ui::statForm stat; - stat.setupUi(&statDialog); 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.medianOUT->setText(QString::number(image_data->getMedian(), 'f', 3)); stat.SNROUT->setText(QString::number(image_data->getSNR(), 'f', 3)); +} - statDialog.exec(); +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::headerFITS() +void FITSTab::loadFITSHeader() { FITSData *image_data = view->getImageData(); - QDialog fitsHeaderDialog; - Ui::fitsHeaderDialog header; int nkeys = image_data->getRecords().size(); int counter=0; - header.setupUi(&fitsHeaderDialog); 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->resizeColumnsToContents(); - fitsHeaderDialog.exec(); + 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; char err_text[FLEN_STATUS]; if ((err_status = saveFITS('!' + currentURL.toLocalFile())) != 0) { // -1000 = user canceled if (err_status == -1000) return false; fits_get_errstatus(err_status, err_text); // Use KMessageBox or something here KMessageBox::error(nullptr, 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()); KMessageBox::sorry(nullptr, 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 0e90bd7e7..7a332ef73 100644 --- a/kstars/fitsviewer/fitstab.h +++ b/kstars/fitsviewer/fitstab.h @@ -1,107 +1,125 @@ /*************************************************************************** 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 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(); 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 FITSHistogram *getHistogram() { return histogram; } - inline FITSViewer *getViewer() { return viewer; } + 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); 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; + /// FITS image object std::unique_ptr view; - /// FITS Histogram - FITSHistogram *histogram { nullptr }; - FITSViewer *viewer { nullptr }; /// 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 26e845d82..e5982ce13 100644 --- a/kstars/fitsviewer/statform.ui +++ b/kstars/fitsviewer/statform.ui @@ -1,182 +1,210 @@ statForm 0 0 - 311 - 227 + 172 + 390 Statistics - + + 4 + + + 4 + + + 4 + + + 4 + + + -1 + + + + + Standard Deviation + + + Std. dev: + + + + + + + Height: + + + + + + + Bitpix: + + + + Width: - - + + true - - - - Height: + + + + true - - + + true - - + + - Min: + + + + true - - + + true - - - - Max: + + + + true - + true - - - - Bitpix: + + + + true - - + + true - - - - Standard Deviation - - - Std. dev: + + + + true - - - - true + + + + Min: - + Mean: - - - - true + + + + Max: - + Median - - - - true - - - - + Half-Flux Radius HFR: - - - - true - - - - + Signal to Noise Ratio SNR: - - - - + + + + Qt::Vertical - - true + + + 20 + 40 + - + widthOUT heightOUT bitpixOUT maxOUT minOUT meanOUT stddevOUT