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 }