File indexing completed on 2024-04-21 14:45:48

0001 /*
0002     SPDX-FileCopyrightText: 2015 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "fitshistogram.h"
0008 
0009 #include "fits_debug.h"
0010 
0011 #include "Options.h"
0012 #include "fitsdata.h"
0013 #include "fitstab.h"
0014 #include "fitsview.h"
0015 #include "fitsviewer.h"
0016 
0017 #include <KMessageBox>
0018 
0019 #include <QtConcurrent>
0020 #include <type_traits>
0021 #include <zlib.h>
0022 
0023 histogramUI::histogramUI(QDialog * parent) : QDialog(parent)
0024 {
0025     setupUi(parent);
0026     setModal(false);
0027 }
0028 
0029 FITSHistogram::FITSHistogram(QWidget * parent) : QDialog(parent)
0030 {
0031     ui = new histogramUI(this);
0032     tab = dynamic_cast<FITSTab *>(parent);
0033 
0034     customPlot = ui->histogramPlot;
0035 
0036     customPlot->setBackground(QBrush(Qt::black));
0037 
0038     customPlot->xAxis->setBasePen(QPen(Qt::white, 1));
0039     customPlot->yAxis->setBasePen(QPen(Qt::white, 1));
0040 
0041     customPlot->xAxis->setTickPen(QPen(Qt::white, 1));
0042     customPlot->yAxis->setTickPen(QPen(Qt::white, 1));
0043 
0044     customPlot->xAxis->setSubTickPen(QPen(Qt::white, 1));
0045     customPlot->yAxis->setSubTickPen(QPen(Qt::white, 1));
0046 
0047     customPlot->xAxis->setTickLabelColor(Qt::white);
0048     customPlot->yAxis->setTickLabelColor(Qt::white);
0049 
0050     customPlot->xAxis->setLabelColor(Qt::white);
0051     customPlot->yAxis->setLabelColor(Qt::white);
0052 
0053     // Reserve 3 channels
0054     cumulativeFrequency.resize(3);
0055     intensity.resize(3);
0056     frequency.resize(3);
0057 
0058     FITSMin.fill(0, 3);
0059     FITSMax.fill(0, 3);
0060     binWidth.fill(0, 3);
0061 
0062     rgbWidgets.resize(3);
0063     rgbWidgets[RED_CHANNEL] << ui->RLabel << ui->minREdit << ui->redSlider
0064                             << ui->maxREdit;
0065     rgbWidgets[GREEN_CHANNEL] << ui->GLabel << ui->minGEdit << ui->greenSlider
0066                               << ui->maxGEdit;
0067     rgbWidgets[BLUE_CHANNEL] << ui->BLabel << ui->minBEdit << ui->blueSlider
0068                              << ui->maxBEdit;
0069 
0070     minBoxes << ui->minREdit << ui->minGEdit << ui->minBEdit;
0071     maxBoxes << ui->maxREdit << ui->maxGEdit << ui->maxBEdit;
0072     sliders << ui->redSlider << ui->greenSlider << ui->blueSlider;
0073 
0074     customPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
0075     customPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
0076     customPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
0077     customPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
0078     customPlot->xAxis->grid()->setZeroLinePen(Qt::NoPen);
0079     customPlot->yAxis->grid()->setZeroLinePen(Qt::NoPen);
0080 
0081     connect(ui->applyB, &QPushButton::clicked, this, &FITSHistogram::applyScale);
0082     connect(ui->hideSaturated, &QCheckBox::stateChanged, [this]()
0083     {
0084         constructHistogram();
0085     });
0086 
0087     //    connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), this,
0088     //            SLOT(checkRangeLimit(QCPRange)));
0089     connect(customPlot, &QCustomPlot::mouseMove, this,
0090             &FITSHistogram::driftMouseOverLine);
0091 
0092     for (int i = 0; i < 3; i++)
0093     {
0094         // Box --> Slider
0095         QVector<QWidget *> w = rgbWidgets[i];
0096         connect(qobject_cast<QDoubleSpinBox *>(w[1]), &QDoubleSpinBox::editingFinished, [this, i, w]()
0097         {
0098             double value = qobject_cast<QDoubleSpinBox *>(w[1])->value();
0099             w[2]->blockSignals(true);
0100             qobject_cast<ctkRangeSlider *>(w[2])->setMinimumPosition((value - FITSMin[i])*sliderScale[i]);
0101             w[2]->blockSignals(false);
0102         });
0103         connect(qobject_cast<QDoubleSpinBox *>(w[3]), &QDoubleSpinBox::editingFinished, [this, i, w]()
0104         {
0105             double value = qobject_cast<QDoubleSpinBox *>(w[3])->value();
0106             w[2]->blockSignals(true);
0107             qobject_cast<ctkRangeSlider *>(w[2])->setMaximumPosition((value - FITSMin[i] - sliderTick[i])*sliderScale[i]);
0108             w[2]->blockSignals(false);
0109         });
0110 
0111         // Slider --> Box
0112         connect(qobject_cast<ctkRangeSlider *>(w[2]), &ctkRangeSlider::minimumValueChanged, [this, i, w](int position)
0113         {
0114             qobject_cast<QDoubleSpinBox *>(w[1])->setValue(FITSMin[i] + (position / sliderScale[i]));
0115         });
0116         connect(qobject_cast<ctkRangeSlider *>(w[2]), &ctkRangeSlider::maximumValueChanged, [this, i, w](int position)
0117         {
0118             qobject_cast<QDoubleSpinBox *>(w[3])->setValue(FITSMin[i] + sliderTick[i] + (position / sliderScale[i]));
0119         });
0120     }
0121 
0122 }
0123 
0124 void FITSHistogram::showEvent(QShowEvent * event)
0125 {
0126     Q_UNUSED(event)
0127     if (!m_Constructed)
0128         constructHistogram();
0129     syncGUI();
0130 }
0131 
0132 void FITSHistogram::constructHistogram()
0133 {
0134     const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
0135 
0136     isGUISynced = false;
0137 
0138     switch (imageData->getStatistics().dataType)
0139     {
0140         case TBYTE:
0141             constructHistogram<uint8_t>();
0142             break;
0143 
0144         case TSHORT:
0145             constructHistogram<int16_t>();
0146             break;
0147 
0148         case TUSHORT:
0149             constructHistogram<uint16_t>();
0150             break;
0151 
0152         case TLONG:
0153             constructHistogram<int32_t>();
0154             break;
0155 
0156         case TULONG:
0157             constructHistogram<uint32_t>();
0158             break;
0159 
0160         case TFLOAT:
0161             constructHistogram<float>();
0162             break;
0163 
0164         case TLONGLONG:
0165             constructHistogram<int64_t>();
0166             break;
0167 
0168         case TDOUBLE:
0169             constructHistogram<double>();
0170             break;
0171 
0172         default:
0173             break;
0174     }
0175 
0176     m_Constructed = true;
0177     if (isVisible())
0178         syncGUI();
0179 }
0180 
0181 template <typename T> void FITSHistogram::constructHistogram()
0182 {
0183     const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
0184     uint16_t width = imageData->width(), height = imageData->height();
0185     uint8_t channels = imageData->channels();
0186 
0187     auto * const buffer = reinterpret_cast<T const *>(imageData->getImageBuffer());
0188 
0189     double min, max;
0190     for (int i = 0 ; i < 3; i++)
0191     {
0192         imageData->getMinMax(&min, &max, i);
0193         FITSMin[i] = min;
0194         FITSMax[i] = max;
0195     }
0196 
0197     uint32_t samples = width * height;
0198     const uint32_t sampleBy = samples > 1000000 ? samples / 1000000 : 1;
0199 
0200     //binCount = static_cast<uint16_t>(sqrt(samples));
0201     binCount = qMin(FITSMax[0] - FITSMin[0], 400.0);
0202     if (binCount <= 0)
0203         binCount = 400;
0204 
0205     for (int n = 0; n < channels; n++)
0206     {
0207         intensity[n].fill(0, binCount);
0208         frequency[n].fill(0, binCount);
0209         cumulativeFrequency[n].fill(0, binCount);
0210         binWidth[n] = (FITSMax[n] - FITSMin[n]) / (binCount - 1);
0211         // Initialize the median to 0 in case the computation below fails.
0212         imageData->setMedian(0, n);
0213     }
0214 
0215     QVector<QFuture<void>> futures;
0216 
0217     for (int n = 0; n < channels; n++)
0218     {
0219         futures.append(QtConcurrent::run([ = ]()
0220         {
0221             for (int i = 0; i < binCount; i++)
0222                 intensity[n][i] = FITSMin[n] + (binWidth[n] * i);
0223         }));
0224     }
0225 
0226     for (int n = 0; n < channels; n++)
0227     {
0228         futures.append(QtConcurrent::run([ = ]()
0229         {
0230             uint32_t offset = n * samples;
0231 
0232             for (uint32_t i = 0; i < samples; i += sampleBy)
0233             {
0234                 int32_t id = rint((buffer[i + offset] - FITSMin[n]) / binWidth[n]);
0235                 if (id < 0)
0236                     id = 0;
0237                 frequency[n][id] += sampleBy;
0238             }
0239         }));
0240     }
0241 
0242     for (QFuture<void> future : futures)
0243         future.waitForFinished();
0244 
0245     futures.clear();
0246 
0247     for (int n = 0; n < channels; n++)
0248     {
0249         futures.append(QtConcurrent::run([ = ]()
0250         {
0251             uint32_t accumulator = 0;
0252             for (int i = 0; i < binCount; i++)
0253             {
0254                 accumulator += frequency[n][i];
0255                 cumulativeFrequency[n].replace(i, accumulator);
0256             }
0257         }));
0258     }
0259 
0260     for (QFuture<void> future : futures)
0261         future.waitForFinished();
0262 
0263     futures.clear();
0264 
0265     for (int n = 0; n < channels; n++)
0266     {
0267         futures.append(QtConcurrent::run([ = ]()
0268         {
0269             double median[3] = {0};
0270             const bool cutoffSpikes = ui->hideSaturated->isChecked();
0271             const uint32_t halfCumulative = cumulativeFrequency[n][binCount - 1] / 2;
0272 
0273             // Find which bin contains the median.
0274             int median_bin = -1;
0275             for (int i = 0; i < binCount; i++)
0276             {
0277                 if (cumulativeFrequency[n][i] > halfCumulative)
0278                 {
0279                     median_bin = i;
0280                     break;
0281                 }
0282             }
0283 
0284             if (median_bin >= 0)
0285             {
0286                 // The number of items in the median bin
0287                 const uint32_t median_bin_size = frequency[n][median_bin] / sampleBy;
0288                 if (median_bin_size > 0)
0289                 {
0290                     // The median is this element inside the sorted median_bin;
0291                     const uint32_t samples_before_median_bin = median_bin == 0 ? 0 : cumulativeFrequency[n][median_bin - 1];
0292                     uint32_t median_position = (halfCumulative - samples_before_median_bin) / sampleBy;
0293 
0294                     if (median_position >= median_bin_size)
0295                         median_position = median_bin_size - 1;
0296                     if (median_position >= 0 && median_position < median_bin_size)
0297                     {
0298                         // Fill a vector with the values in the median bin (sampled by sampleBy).
0299                         std::vector<T> median_bin_samples(median_bin_size);
0300                         uint32_t upto = 0;
0301                         const uint32_t offset = n * samples;
0302                         for (uint32_t i = 0; i < samples; i += sampleBy)
0303                         {
0304                             if (upto >= median_bin_size) break;
0305                             const int32_t id = rint((buffer[i + offset] - FITSMin[n]) / binWidth[n]);
0306                             if (id == median_bin)
0307                                 median_bin_samples[upto++] = buffer[i + offset];
0308                         }
0309                         // Get the Nth value using N = the median position.
0310                         if (upto > 0)
0311                         {
0312                             if (median_position >= upto) median_position = upto - 1;
0313                             std::nth_element(median_bin_samples.begin(), median_bin_samples.begin() + median_position,
0314                                              median_bin_samples.begin() + upto);
0315                             median[n] = median_bin_samples[median_position];
0316                         }
0317                     }
0318                 }
0319             }
0320 
0321             imageData->setMedian(median[n], n);
0322 
0323             if (cutoffSpikes)
0324             {
0325                 QVector<double> sortedFreq = frequency[n];
0326                 std::sort(sortedFreq.begin(), sortedFreq.end());
0327                 double cutoff = sortedFreq[binCount * 0.99];
0328                 for (int i = 0; i < binCount; i++)
0329                 {
0330                     if (frequency[n][i] >= cutoff)
0331                         frequency[n][i] = cutoff;
0332                 }
0333             }
0334 
0335         }));
0336     }
0337 
0338     for (QFuture<void> future : futures)
0339         future.waitForFinished();
0340 
0341     // Custom index to indicate the overall contrast of the image
0342     if (cumulativeFrequency[RED_CHANNEL][binCount / 4] > 0)
0343         JMIndex = cumulativeFrequency[RED_CHANNEL][binCount / 8] / static_cast<double>(cumulativeFrequency[RED_CHANNEL][binCount /
0344                   4]);
0345     else
0346         JMIndex = 1;
0347     qCDebug(KSTARS_FITS) << "FITHistogram: JMIndex " << JMIndex;
0348 
0349     sliderTick.clear();
0350     sliderScale.clear();
0351     for (int n = 0; n < channels; n++)
0352     {
0353         sliderTick  << fabs(FITSMax[n] - FITSMin[n]) / 99.0;
0354         sliderScale << 99.0 / (FITSMax[n] - FITSMin[n] - sliderTick[n]);
0355     }
0356 }
0357 
0358 void FITSHistogram::syncGUI()
0359 {
0360     if (isGUISynced)
0361         return;
0362 
0363     const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
0364     bool isColor = imageData->channels() > 1;
0365     // R/K is always enabled
0366     for (auto w : rgbWidgets[RED_CHANNEL])
0367         w->setEnabled(true);
0368     // G Channel
0369     for (auto w : rgbWidgets[GREEN_CHANNEL])
0370         w->setEnabled(isColor);
0371     // B Channel
0372     for (auto w : rgbWidgets[BLUE_CHANNEL])
0373         w->setEnabled(isColor);
0374 
0375     ui->meanEdit->setText(QString::number(imageData->getMean()));
0376     ui->medianEdit->setText(QString::number(imageData->getMedian()));
0377 
0378     for (int n = 0; n < imageData->channels(); n++)
0379     {
0380         double median = imageData->getMedian(n);
0381 
0382         if (median > 100)
0383             numDecimals << 0;
0384         else if (median > 1)
0385             numDecimals << 2;
0386         else if (median > .01)
0387             numDecimals << 4;
0388         else if (median > .0001)
0389             numDecimals << 6;
0390         else
0391             numDecimals << 10;
0392 
0393         minBoxes[n]->setDecimals(numDecimals[n]);
0394         minBoxes[n]->setSingleStep(fabs(FITSMax[n] - FITSMin[n]) / 20.0);
0395         minBoxes[n]->setMinimum(FITSMin[n]);
0396         minBoxes[n]->setMaximum(FITSMax[n] - sliderTick[n]);
0397         minBoxes[n]->setValue(FITSMin[n] + (sliders[n]->minimumValue() / sliderScale[n]));
0398 
0399         maxBoxes[n]->setDecimals(numDecimals[n]);
0400         maxBoxes[n]->setSingleStep(fabs(FITSMax[n] - FITSMin[n]) / 20.0);
0401         maxBoxes[n]->setMinimum(FITSMin[n] + sliderTick[n]);
0402         maxBoxes[n]->setMaximum(FITSMax[n]);
0403         maxBoxes[n]->setValue(FITSMin[n] + sliderTick[n] + (sliders[n]->maximumValue() / sliderScale[n]));
0404     }
0405 
0406     customPlot->clearGraphs();
0407     graphs.clear();
0408 
0409     for (int n = 0; n < imageData->channels(); n++)
0410     {
0411         graphs.append(customPlot->addGraph());
0412         graphs[n]->setData(intensity[n], frequency[n]);
0413     }
0414 
0415     graphs[RED_CHANNEL]->setBrush(QBrush(QColor(170, 40, 80)));
0416     graphs[RED_CHANNEL]->setPen(QPen(Qt::red));
0417 
0418     if (isColor)
0419     {
0420         graphs[GREEN_CHANNEL]->setBrush(QBrush(QColor(80, 40, 170)));
0421         graphs[GREEN_CHANNEL]->setPen(QPen(Qt::green));
0422 
0423         graphs[BLUE_CHANNEL]->setBrush(QBrush(QColor(170, 40, 80)));
0424         graphs[BLUE_CHANNEL]->setPen(QPen(Qt::blue));
0425     }
0426 
0427     customPlot->axisRect(0)->setRangeDrag(Qt::Horizontal);
0428     customPlot->axisRect(0)->setRangeZoom(Qt::Horizontal);
0429 
0430     customPlot->xAxis->setLabel(i18n("Intensity"));
0431     customPlot->yAxis->setLabel(i18n("Frequency"));
0432 
0433     //    customPlot->xAxis->setRange(fits_min - ui->minEdit->singleStep(),
0434     //                                fits_max + ui->maxEdit->singleStep());
0435 
0436     customPlot->xAxis->rescale();
0437     customPlot->yAxis->rescale();
0438 
0439     customPlot->setInteraction(QCP::iRangeDrag, true);
0440     customPlot->setInteraction(QCP::iRangeZoom, true);
0441     customPlot->setInteraction(QCP::iSelectPlottables, true);
0442 
0443     customPlot->replot();
0444     resizePlot();
0445 
0446     isGUISynced = true;
0447 }
0448 
0449 void FITSHistogram::resizePlot()
0450 {
0451     if (!m_Constructed)
0452         constructHistogram();
0453 
0454     if (customPlot->width() < 300)
0455         customPlot->yAxis->setTickLabels(false);
0456     else
0457         customPlot->yAxis->setTickLabels(true);
0458     customPlot->xAxis->ticker()->setTickCount(customPlot->width() / 100);
0459 }
0460 
0461 double FITSHistogram::getJMIndex() const
0462 {
0463     return JMIndex;
0464 }
0465 
0466 void FITSHistogram::applyScale()
0467 {
0468     QVector<double> min, max;
0469 
0470     min << minBoxes[0]->value() << minBoxes[1]->value() <<  minBoxes[2]->value();
0471     max << maxBoxes[0]->value() << maxBoxes[1]->value() << maxBoxes[2]->value();
0472 
0473     FITSHistogramCommand * histC;
0474 
0475     if (ui->logR->isChecked())
0476         type = FITS_LOG;
0477     else
0478         type = FITS_LINEAR;
0479 
0480     histC = new FITSHistogramCommand(tab, this, type, min, max);
0481 
0482     tab->getUndoStack()->push(histC);
0483 }
0484 
0485 void FITSHistogram::applyFilter(FITSScale ftype)
0486 {
0487     QVector<double> min, max;
0488 
0489     min.append(ui->minREdit->value());
0490 
0491     FITSHistogramCommand * histC;
0492 
0493     type = ftype;
0494 
0495     histC = new FITSHistogramCommand(tab, this, type, min, max);
0496 
0497     tab->getUndoStack()->push(histC);
0498 }
0499 
0500 QVector<uint32_t> FITSHistogram::getCumulativeFrequency(int channel) const
0501 {
0502     return cumulativeFrequency[channel];
0503 }
0504 
0505 FITSHistogramCommand::FITSHistogramCommand(QWidget * parent,
0506         FITSHistogram * inHisto,
0507         FITSScale newType,
0508         const QVector<double> &lmin,
0509         const QVector<double> &lmax)
0510 {
0511     tab = dynamic_cast<FITSTab *>(parent);
0512     type = newType;
0513     histogram = inHisto;
0514     min = lmin;
0515     max = lmax;
0516 }
0517 
0518 FITSHistogramCommand::~FITSHistogramCommand()
0519 {
0520     delete[] delta;
0521 }
0522 
0523 bool FITSHistogramCommand::calculateDelta(const uint8_t * buffer)
0524 {
0525     const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
0526 
0527     uint8_t const * image_buffer = imageData->getImageBuffer();
0528     int totalPixels =
0529         imageData->width() * imageData->height() * imageData->channels();
0530     unsigned long totalBytes = totalPixels * imageData->getBytesPerPixel();
0531 
0532     auto * raw_delta = new uint8_t[totalBytes];
0533 
0534     if (raw_delta == nullptr)
0535     {
0536         qWarning() << "Error! not enough memory to create image delta" << endl;
0537         return false;
0538     }
0539 
0540     for (unsigned int i = 0; i < totalBytes; i++)
0541         raw_delta[i] = buffer[i] ^ image_buffer[i];
0542 
0543     compressedBytes = sizeof(uint8_t) * totalBytes + totalBytes / 64 + 16 + 3;
0544     delete[] delta;
0545     delta = new uint8_t[compressedBytes];
0546 
0547     if (delta == nullptr)
0548     {
0549         delete[] raw_delta;
0550         qCCritical(KSTARS_FITS)
0551                 << "FITSHistogram Error: Ran out of memory compressing delta";
0552         return false;
0553     }
0554 
0555     int r = compress2(delta, &compressedBytes, raw_delta, totalBytes, 5);
0556 
0557     if (r != Z_OK)
0558     {
0559         delete[] raw_delta;
0560         /* this should NEVER happen */
0561         qCCritical(KSTARS_FITS)
0562                 << "FITSHistogram Error: Failed to compress raw_delta";
0563         return false;
0564     }
0565 
0566     // qDebug() << "compressed bytes size " << compressedBytes << " bytes" <<
0567     // endl;
0568 
0569     delete[] raw_delta;
0570 
0571     return true;
0572 }
0573 
0574 bool FITSHistogramCommand::reverseDelta()
0575 {
0576     FITSView * image = tab->getView();
0577     const QSharedPointer<FITSData> &imageData = image->imageData();
0578     uint8_t const * image_buffer = (imageData->getImageBuffer());
0579 
0580     int totalPixels =
0581         imageData->width() * imageData->height() * imageData->channels();
0582     unsigned long totalBytes = totalPixels * imageData->getBytesPerPixel();
0583 
0584     auto * output_image = new uint8_t[totalBytes];
0585 
0586     if (output_image == nullptr)
0587     {
0588         qWarning() << "Error! not enough memory to create output image" << endl;
0589         return false;
0590     }
0591 
0592     auto * raw_delta = new uint8_t[totalBytes];
0593 
0594     if (raw_delta == nullptr)
0595     {
0596         delete[] output_image;
0597         qWarning() << "Error! not enough memory to create image delta" << endl;
0598         return false;
0599     }
0600 
0601     int r = uncompress(raw_delta, &totalBytes, delta, compressedBytes);
0602     if (r != Z_OK)
0603     {
0604         qCCritical(KSTARS_FITS)
0605                 << "FITSHistogram compression error in reverseDelta()";
0606         delete[] output_image;
0607         delete[] raw_delta;
0608         return false;
0609     }
0610 
0611     for (unsigned int i = 0; i < totalBytes; i++)
0612         output_image[i] = raw_delta[i] ^ image_buffer[i];
0613 
0614     imageData->setImageBuffer(output_image);
0615 
0616     delete[] raw_delta;
0617 
0618     return true;
0619 }
0620 
0621 void FITSHistogramCommand::redo()
0622 {
0623     FITSView * image = tab->getView();
0624     const QSharedPointer<FITSData> &imageData = image->imageData();
0625 
0626     uint8_t const * image_buffer = imageData->getImageBuffer();
0627     uint8_t * buffer = nullptr;
0628     unsigned int size =
0629         imageData->width() * imageData->height() * imageData->channels();
0630     int BBP = imageData->getBytesPerPixel();
0631 
0632     QApplication::setOverrideCursor(Qt::WaitCursor);
0633 
0634     if (delta != nullptr)
0635     {
0636         FITSImage::Statistic prevStats;
0637         imageData->saveStatistics(prevStats);
0638 
0639         reverseDelta();
0640 
0641         imageData->restoreStatistics(stats);
0642 
0643         stats = prevStats;
0644     }
0645     else
0646     {
0647         imageData->saveStatistics(stats);
0648 
0649         // If it's rotation of flip, no need to calculate delta
0650         if (type >= FITS_ROTATE_CW && type <= FITS_FLIP_V)
0651         {
0652             imageData->applyFilter(type);
0653         }
0654         else
0655         {
0656             buffer = new uint8_t[size * BBP];
0657 
0658             if (buffer == nullptr)
0659             {
0660                 qWarning()
0661                         << "Error! not enough memory to create image buffer in redo()"
0662                         << endl;
0663                 QApplication::restoreOverrideCursor();
0664                 return;
0665             }
0666 
0667             memcpy(buffer, image_buffer, size * BBP);
0668 
0669             QVector<double> dataMin = min, dataMax = max;
0670             switch (type)
0671             {
0672                 case FITS_AUTO:
0673                 case FITS_LINEAR:
0674                     imageData->applyFilter(FITS_LINEAR, nullptr, &dataMin, &dataMax);
0675                     break;
0676 
0677                 case FITS_LOG:
0678                     imageData->applyFilter(FITS_LOG, nullptr, &dataMin, &dataMax);
0679                     break;
0680 
0681                 case FITS_SQRT:
0682                     imageData->applyFilter(FITS_SQRT, nullptr, &dataMin, &dataMax);
0683                     break;
0684 
0685                 default:
0686                     imageData->applyFilter(type);
0687                     break;
0688             }
0689 
0690             calculateDelta(buffer);
0691             delete[] buffer;
0692         }
0693     }
0694 
0695     if (histogram != nullptr)
0696     {
0697         histogram->constructHistogram();
0698 
0699         if (tab->getViewer()->isStarsMarked())
0700             imageData->findStars().waitForFinished();
0701     }
0702 
0703     image->pushFilter(type);
0704     image->rescale(ZOOM_KEEP_LEVEL);
0705     image->updateFrame();
0706 
0707     QApplication::restoreOverrideCursor();
0708 }
0709 
0710 void FITSHistogramCommand::undo()
0711 {
0712     FITSView * image = tab->getView();
0713     const QSharedPointer<FITSData> &imageData = image->imageData();
0714 
0715     QApplication::setOverrideCursor(Qt::WaitCursor);
0716 
0717     if (delta != nullptr)
0718     {
0719         FITSImage::Statistic prevStats;
0720         imageData->saveStatistics(prevStats);
0721 
0722         reverseDelta();
0723 
0724         imageData->restoreStatistics(stats);
0725 
0726         stats = prevStats;
0727     }
0728     else
0729     {
0730         switch (type)
0731         {
0732             case FITS_ROTATE_CW:
0733                 imageData->applyFilter(FITS_ROTATE_CCW);
0734                 break;
0735             case FITS_ROTATE_CCW:
0736                 imageData->applyFilter(FITS_ROTATE_CW);
0737                 break;
0738             case FITS_FLIP_H:
0739             case FITS_FLIP_V:
0740                 imageData->applyFilter(type);
0741                 break;
0742             default:
0743                 break;
0744         }
0745     }
0746 
0747     if (histogram != nullptr)
0748     {
0749         histogram->constructHistogram();
0750 
0751         if (tab->getViewer()->isStarsMarked())
0752             imageData->findStars().waitForFinished();
0753     }
0754 
0755     image->popFilter();
0756     image->rescale(ZOOM_KEEP_LEVEL);
0757     image->updateFrame();
0758 
0759     QApplication::restoreOverrideCursor();
0760 }
0761 
0762 QString FITSHistogramCommand::text() const
0763 {
0764     switch (type)
0765     {
0766         case FITS_AUTO:
0767             return i18n("Auto Scale");
0768         case FITS_LINEAR:
0769             return i18n("Linear Scale");
0770         case FITS_LOG:
0771             return i18n("Logarithmic Scale");
0772         case FITS_SQRT:
0773             return i18n("Square Root Scale");
0774 
0775         default:
0776             if (type - 1 <= FITSViewer::filterTypes.count())
0777                 return FITSViewer::filterTypes.at(type - 1);
0778             break;
0779     }
0780 
0781     return i18n("Unknown");
0782 }
0783 
0784 void FITSHistogram::driftMouseOverLine(QMouseEvent * event)
0785 {
0786     double intensity = customPlot->xAxis->pixelToCoord(event->localPos().x());
0787 
0788     const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
0789     uint8_t channels = imageData->channels();
0790     QVector<double> freq(3, -1);
0791 
0792     QVector<bool> inRange(3, false);
0793     for (int n = 0; n < channels; n++)
0794     {
0795         if (intensity >= imageData->getMin(n) && intensity <= imageData->getMax(n))
0796             inRange[n] = true;
0797     }
0798 
0799     if ( (channels == 1 && inRange[0] == false) || (!inRange[0] && !inRange[1] && !inRange[2]) )
0800     {
0801         QToolTip::hideText();
0802         return;
0803     }
0804 
0805     if (customPlot->xAxis->range().contains(intensity))
0806     {
0807         for (int n = 0; n < channels; n++)
0808         {
0809             int index = graphs[n]->findBegin(intensity, true);
0810             freq[n] = graphs[n]->dataMainValue(index);
0811         }
0812 
0813         if (channels == 1 && freq[0] > 0)
0814         {
0815             QToolTip::showText(
0816                 event->globalPos(),
0817                 i18nc("Histogram tooltip; %1 is intensity; %2 is frequency;",
0818                       "<table>"
0819                       "<tr><td>Intensity:   </td><td>%1</td></tr>"
0820                       "<tr><td>R Frequency:   </td><td>%2</td></tr>"
0821                       "</table>",
0822                       QString::number(intensity, 'f', numDecimals[0]),
0823                       QString::number(freq[0], 'f', 0)));
0824         }
0825         else if (freq[1] > 0)
0826         {
0827             QToolTip::showText(
0828                 event->globalPos(),
0829                 i18nc("Histogram tooltip; %1 is intensity; %2 is frequency;",
0830                       "<table>"
0831                       "<tr><td>Intensity:   </td><td>%1</td></tr>"
0832                       "<tr><td>R Frequency:   </td><td>%2</td></tr>"
0833                       "<tr><td>G Frequency:   </td><td>%3</td></tr>"
0834                       "<tr><td>B Frequency:   </td><td>%4</td></tr>"
0835                       "</table>",
0836                       QString::number(intensity, 'f', numDecimals[0]),
0837                       QString::number(freq[0], 'f', 0),
0838                       QString::number(freq[1], 'f', 0),
0839                       QString::number(freq[2], 'f', 0)));
0840         }
0841         else
0842             QToolTip::hideText();
0843 
0844         customPlot->replot();
0845     }
0846 }