File indexing completed on 2024-04-14 14:10:36

0001 /*
0002     SPDX-FileCopyrightText: 2021 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "fitshistogramview.h"
0008 
0009 #include "fits_debug.h"
0010 
0011 #include "fitsdata.h"
0012 #include "fitstab.h"
0013 #include "fitsview.h"
0014 #include "fitsviewer.h"
0015 
0016 #include <KMessageBox>
0017 
0018 #include <QtConcurrent>
0019 #include <type_traits>
0020 
0021 FITSHistogramView::FITSHistogramView(QWidget *parent) : QCustomPlot(parent)
0022 {
0023     setBackground(QBrush(Qt::black));
0024 
0025     xAxis->setBasePen(QPen(Qt::white, 1));
0026     yAxis->setBasePen(QPen(Qt::white, 1));
0027 
0028     xAxis->setTickPen(QPen(Qt::white, 1));
0029     yAxis->setTickPen(QPen(Qt::white, 1));
0030 
0031     xAxis->setSubTickPen(QPen(Qt::white, 1));
0032     yAxis->setSubTickPen(QPen(Qt::white, 1));
0033 
0034     xAxis->setTickLabelColor(Qt::white);
0035     yAxis->setTickLabelColor(Qt::white);
0036 
0037     xAxis->setLabelColor(Qt::white);
0038     yAxis->setLabelColor(Qt::white);
0039 
0040     xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
0041     yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
0042     xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
0043     yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
0044     xAxis->grid()->setZeroLinePen(Qt::NoPen);
0045     yAxis->grid()->setZeroLinePen(Qt::NoPen);
0046 
0047     connect(this, &QCustomPlot::mouseMove, this, &FITSHistogramView::driftMouseOverLine);
0048     connect(xAxis, SIGNAL(rangeChanged(QCPRange)), this,   SLOT(onXRangeChanged(QCPRange)));
0049     connect(yAxis, SIGNAL(rangeChanged(QCPRange)), this,   SLOT(onYRangeChanged(QCPRange)));
0050 
0051     m_HistogramIntensity.resize(3);
0052     m_HistogramFrequency.resize(3);
0053     for (int i = 0; i < 3; i++)
0054     {
0055         m_HistogramIntensity[i].resize(256);
0056         for (int j = 0; j < 256; j++)
0057             m_HistogramIntensity[i][j] = j;
0058         m_HistogramFrequency[i].resize(256);
0059     }
0060 }
0061 
0062 void FITSHistogramView::showEvent(QShowEvent * event)
0063 {
0064     Q_UNUSED(event)
0065     if (m_ImageData.isNull())
0066         return;
0067     if (!m_ImageData->isHistogramConstructed())
0068     {
0069         if (m_Linear)
0070             m_ImageData->constructHistogram();
0071         else
0072             createNonLinearHistogram();
0073     }
0074     syncGUI();
0075 }
0076 
0077 void FITSHistogramView::reset()
0078 {
0079     isGUISynced = false;
0080 }
0081 
0082 void FITSHistogramView::syncGUI()
0083 {
0084     if (isGUISynced)
0085         return;
0086 
0087     bool isColor = m_ImageData->channels() > 1;
0088 
0089     clearGraphs();
0090     graphs.clear();
0091 
0092     for (int n = 0; n < m_ImageData->channels(); n++)
0093     {
0094         graphs.append(addGraph());
0095 
0096         if (!m_Linear)
0097         {
0098             graphs[n]->setData(m_HistogramIntensity[n], m_HistogramFrequency[n]);
0099             numDecimals << 0;
0100         }
0101         else
0102         {
0103             graphs[n]->setData(m_ImageData->getHistogramIntensity(n), m_ImageData->getHistogramFrequency(n));
0104 
0105             double median = m_ImageData->getMedian(n);
0106 
0107             if (median > 100)
0108                 numDecimals << 0;
0109             else if (median > 1)
0110                 numDecimals << 2;
0111             else if (median > .01)
0112                 numDecimals << 4;
0113             else if (median > .0001)
0114                 numDecimals << 6;
0115             else
0116                 numDecimals << 10;
0117         }
0118     }
0119 
0120     graphs[RED_CHANNEL]->setBrush(QBrush(QColor(170, 40, 80)));
0121     graphs[RED_CHANNEL]->setPen(QPen(Qt::red));
0122 
0123     if (isColor)
0124     {
0125         graphs[GREEN_CHANNEL]->setBrush(QBrush(QColor(80, 40, 170)));
0126         graphs[GREEN_CHANNEL]->setPen(QPen(Qt::green));
0127 
0128         graphs[BLUE_CHANNEL]->setBrush(QBrush(QColor(170, 40, 80)));
0129         graphs[BLUE_CHANNEL]->setPen(QPen(Qt::blue));
0130     }
0131 
0132     axisRect(0)->setRangeDrag(Qt::Horizontal);
0133     axisRect(0)->setRangeZoom(Qt::Horizontal);
0134 
0135     if (m_AxesLabelEnabled)
0136     {
0137         xAxis->setLabel(i18n("Intensity"));
0138         yAxis->setLabel(i18n("Frequency"));
0139     }
0140 
0141     //    xAxis->setRange(fits_min - ui->minEdit->singleStep(),
0142     //                                fits_max + ui->maxEdit->singleStep());
0143 
0144     xAxis->rescale();
0145     yAxis->rescale();
0146 
0147     setInteraction(QCP::iRangeDrag, true);
0148     setInteraction(QCP::iRangeZoom, true);
0149     setInteraction(QCP::iSelectPlottables, true);
0150 
0151     replot();
0152     resizePlot();
0153 
0154     isGUISynced = true;
0155 }
0156 
0157 void FITSHistogramView::resizePlot()
0158 {
0159     if (width() < 300)
0160         yAxis->setTickLabels(false);
0161     else
0162         yAxis->setTickLabels(true);
0163     xAxis->ticker()->setTickCount(width() / 100);
0164 }
0165 
0166 void FITSHistogramView::driftMouseOverLine(QMouseEvent * event)
0167 {
0168     double intensity = xAxis->pixelToCoord(event->localPos().x());
0169 
0170     uint8_t channels = m_ImageData->channels();
0171     QVector<double> freq(3, -1);
0172 
0173     if (m_Linear)
0174     {
0175         QVector<bool> inRange(3, false);
0176         for (int n = 0; n < channels; n++)
0177         {
0178             if (intensity >= m_ImageData->getMin(n) && intensity <= m_ImageData->getMax(n))
0179                 inRange[n] = true;
0180         }
0181 
0182         if ( (channels == 1 && inRange[0] == false) || (!inRange[0] && !inRange[1] && !inRange[2]) )
0183         {
0184             QToolTip::hideText();
0185             return;
0186         }
0187     }
0188 
0189     if (xAxis->range().contains(intensity))
0190     {
0191         for (int n = 0; n < channels; n++)
0192         {
0193             int index = graphs[n]->findBegin(intensity, true);
0194             freq[n] = graphs[n]->dataMainValue(index);
0195         }
0196 
0197         if (channels == 1 && freq[0] > 0)
0198         {
0199             QToolTip::showText(
0200                 event->globalPos(),
0201                 i18nc("Histogram tooltip; %1 is intensity; %2 is frequency;",
0202                       "<table>"
0203                       "<tr><td>Intensity:   </td><td>%1</td></tr>"
0204                       "<tr><td>R Frequency:   </td><td>%2</td></tr>"
0205                       "</table>",
0206                       QString::number(intensity, 'f', numDecimals[0]),
0207                       QString::number(freq[0], 'f', 0)));
0208         }
0209         else if (freq[1] > 0)
0210         {
0211             QToolTip::showText(
0212                 event->globalPos(),
0213                 i18nc("Histogram tooltip; %1 is intensity; %2 is frequency;",
0214                       "<table>"
0215                       "<tr><td>Intensity:   </td><td>%1</td></tr>"
0216                       "<tr><td>R Frequency:   </td><td>%2</td></tr>"
0217                       "<tr><td>G Frequency:   </td><td>%3</td></tr>"
0218                       "<tr><td>B Frequency:   </td><td>%4</td></tr>"
0219                       "</table>",
0220                       QString::number(intensity, 'f', numDecimals[0]),
0221                       QString::number(freq[0], 'f', 0),
0222                       QString::number(freq[1], 'f', 0),
0223                       QString::number(freq[2], 'f', 0)));
0224         }
0225         else
0226             QToolTip::hideText();
0227 
0228         replot();
0229     }
0230 }
0231 
0232 void FITSHistogramView::setImageData(const QSharedPointer<FITSData> &data)
0233 {
0234     m_ImageData = data;
0235 
0236     connect(m_ImageData.data(), &FITSData::dataChanged, [this]()
0237     {
0238         if (m_Linear)
0239         {
0240             m_ImageData->resetHistogram();
0241             m_ImageData->constructHistogram();
0242         }
0243         else
0244             createNonLinearHistogram();
0245         isGUISynced = false;
0246         syncGUI();
0247     });
0248 }
0249 
0250 void FITSHistogramView::createNonLinearHistogram()
0251 {
0252     isGUISynced = false;
0253 
0254     int width = m_ImageData->width();
0255     int height = m_ImageData->height();
0256 
0257     const uint8_t channels = m_ImageData->channels();
0258 
0259     QImage rawImage;
0260     if (channels == 1)
0261     {
0262         rawImage = QImage(width, height, QImage::Format_Indexed8);
0263 
0264         rawImage.setColorCount(256);
0265         for (int i = 0; i < 256; i++)
0266             rawImage.setColor(i, qRgb(i, i, i));
0267     }
0268     else
0269     {
0270         rawImage = QImage(width, height, QImage::Format_RGB32);
0271     }
0272 
0273     Stretch stretch(width, height, m_ImageData->channels(), m_ImageData->dataType());
0274     // Compute new auto-stretch params.
0275     StretchParams stretchParams = stretch.computeParams(m_ImageData->getImageBuffer());
0276 
0277     stretch.setParams(stretchParams);
0278     stretch.run(m_ImageData->getImageBuffer(), &rawImage);
0279 
0280     m_HistogramFrequency[0].fill(0);
0281     if (channels > 1)
0282     {
0283         m_HistogramFrequency[1].fill(0);
0284         m_HistogramFrequency[2].fill(0);
0285     }
0286     uint32_t samples = width * height;
0287     const uint32_t sampleBy = (samples > 1000000 ? samples / 1000000 : 1);
0288     if (channels == 1)
0289     {
0290         for (int h = 0; h < height; h += sampleBy)
0291         {
0292             auto * scanLine = rawImage.scanLine(h);
0293             for (int w = 0; w < width; w += sampleBy)
0294                 m_HistogramFrequency[0][scanLine[w]] += sampleBy;
0295         }
0296     }
0297     else
0298     {
0299         for (int h = 0; h < height; h += sampleBy)
0300         {
0301             auto * scanLine = reinterpret_cast<const QRgb *>((rawImage.scanLine(h)));
0302             for (int w = 0; w < width; w += sampleBy)
0303             {
0304                 m_HistogramFrequency[0][qRed(scanLine[w])] += sampleBy;
0305                 m_HistogramFrequency[1][qGreen(scanLine[w])] += sampleBy;
0306                 m_HistogramFrequency[2][qBlue(scanLine[w])] += sampleBy;
0307             }
0308         }
0309     }
0310 
0311     syncGUI();
0312 
0313 }
0314 
0315 void FITSHistogramView::onXRangeChanged(const QCPRange &range)
0316 {
0317     QCPRange boundedRange = range;
0318     if(boundedRange.lower < 0) {  // restrict max zoom in
0319         boundedRange.lower = 0;
0320         boundedRange.upper = boundedRange.size();
0321     }
0322     xAxis->setRange(boundedRange);
0323 }
0324 void FITSHistogramView::onYRangeChanged(const QCPRange &range)
0325 {
0326     QCPRange boundedRange = range;
0327     if(boundedRange.lower < 0) {  // restrict max zoom in
0328         boundedRange.lower = 0;
0329         boundedRange.upper = boundedRange.size();
0330     }
0331     yAxis->setRange(boundedRange);
0332 }