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 }