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
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