Kstars

fitshistogram.cpp
1/*
2 SPDX-FileCopyrightText: 2015 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "fitshistogram.h"
8
9#include "fits_debug.h"
10
11#include "Options.h"
12#include "fitsdata.h"
13#include "fitstab.h"
14#include "fitsview.h"
15#include "fitsviewer.h"
16
17#include <KMessageBox>
18
19#include <QtConcurrent>
20#include <type_traits>
21#include <zlib.h>
22
23histogramUI::histogramUI(QDialog * parent) : QDialog(parent)
24{
25 setupUi(parent);
26 setModal(false);
27}
28
29FITSHistogram::FITSHistogram(QWidget * parent) : QDialog(parent)
30{
31 ui = new histogramUI(this);
32 tab = dynamic_cast<FITSTab *>(parent);
33
34 customPlot = ui->histogramPlot;
35
36 customPlot->setBackground(QBrush(Qt::black));
37
38 customPlot->xAxis->setBasePen(QPen(Qt::white, 1));
39 customPlot->yAxis->setBasePen(QPen(Qt::white, 1));
40
41 customPlot->xAxis->setTickPen(QPen(Qt::white, 1));
42 customPlot->yAxis->setTickPen(QPen(Qt::white, 1));
43
44 customPlot->xAxis->setSubTickPen(QPen(Qt::white, 1));
45 customPlot->yAxis->setSubTickPen(QPen(Qt::white, 1));
46
47 customPlot->xAxis->setTickLabelColor(Qt::white);
48 customPlot->yAxis->setTickLabelColor(Qt::white);
49
50 customPlot->xAxis->setLabelColor(Qt::white);
51 customPlot->yAxis->setLabelColor(Qt::white);
52
53 // Reserve 3 channels
54 cumulativeFrequency.resize(3);
55 intensity.resize(3);
56 frequency.resize(3);
57
58 FITSMin.fill(0, 3);
59 FITSMax.fill(0, 3);
60 binWidth.fill(0, 3);
61
62 rgbWidgets.resize(3);
63 rgbWidgets[RED_CHANNEL] << ui->RLabel << ui->minREdit << ui->redSlider
64 << ui->maxREdit;
65 rgbWidgets[GREEN_CHANNEL] << ui->GLabel << ui->minGEdit << ui->greenSlider
66 << ui->maxGEdit;
67 rgbWidgets[BLUE_CHANNEL] << ui->BLabel << ui->minBEdit << ui->blueSlider
68 << ui->maxBEdit;
69
70 minBoxes << ui->minREdit << ui->minGEdit << ui->minBEdit;
71 maxBoxes << ui->maxREdit << ui->maxGEdit << ui->maxBEdit;
72 sliders << ui->redSlider << ui->greenSlider << ui->blueSlider;
73
74 customPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
75 customPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
76 customPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
77 customPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
78 customPlot->xAxis->grid()->setZeroLinePen(Qt::NoPen);
79 customPlot->yAxis->grid()->setZeroLinePen(Qt::NoPen);
80
81 connect(ui->applyB, &QPushButton::clicked, this, &FITSHistogram::applyScale);
82 connect(ui->hideSaturated, &QCheckBox::stateChanged, [this]()
83 {
84 constructHistogram();
85 });
86
87 // connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), this,
88 // SLOT(checkRangeLimit(QCPRange)));
89 connect(customPlot, &QCustomPlot::mouseMove, this,
90 &FITSHistogram::driftMouseOverLine);
91
92 for (int i = 0; i < 3; i++)
93 {
94 // Box --> Slider
95 QVector<QWidget *> w = rgbWidgets[i];
97 {
98 double value = qobject_cast<QDoubleSpinBox *>(w[1])->value();
99 w[2]->blockSignals(true);
100 qobject_cast<ctkRangeSlider *>(w[2])->setMinimumPosition((value - FITSMin[i])*sliderScale[i]);
101 w[2]->blockSignals(false);
102 });
104 {
105 double value = qobject_cast<QDoubleSpinBox *>(w[3])->value();
106 w[2]->blockSignals(true);
107 qobject_cast<ctkRangeSlider *>(w[2])->setMaximumPosition((value - FITSMin[i] - sliderTick[i])*sliderScale[i]);
108 w[2]->blockSignals(false);
109 });
110
111 // Slider --> Box
113 {
114 qobject_cast<QDoubleSpinBox *>(w[1])->setValue(FITSMin[i] + (position / sliderScale[i]));
115 });
117 {
118 qobject_cast<QDoubleSpinBox *>(w[3])->setValue(FITSMin[i] + sliderTick[i] + (position / sliderScale[i]));
119 });
120 }
121
122}
123
124void FITSHistogram::showEvent(QShowEvent * event)
125{
127 if (!m_Constructed)
128 constructHistogram();
129 syncGUI();
130}
131
132void FITSHistogram::constructHistogram()
133{
134 const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
135
136 isGUISynced = false;
137
138 switch (imageData->getStatistics().dataType)
139 {
140 case TBYTE:
142 break;
143
144 case TSHORT:
146 break;
147
148 case TUSHORT:
150 break;
151
152 case TLONG:
154 break;
155
156 case TULONG:
158 break;
159
160 case TFLOAT:
162 break;
163
164 case TLONGLONG:
166 break;
167
168 case TDOUBLE:
170 break;
171
172 default:
173 break;
174 }
175
176 m_Constructed = true;
177 if (isVisible())
178 syncGUI();
179}
180
181template <typename T> void FITSHistogram::constructHistogram()
182{
183 const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
184 uint16_t width = imageData->width(), height = imageData->height();
185 uint8_t channels = imageData->channels();
186
187 auto * const buffer = reinterpret_cast<T const *>(imageData->getImageBuffer());
188
189 double min, max;
190 for (int i = 0 ; i < 3; i++)
191 {
192 imageData->getMinMax(&min, &max, i);
193 FITSMin[i] = min;
194 FITSMax[i] = max;
195 }
196
197 uint32_t samples = width * height;
198 const uint32_t sampleBy = samples > 1000000 ? samples / 1000000 : 1;
199
200 //binCount = static_cast<uint16_t>(sqrt(samples));
201 binCount = qMin(FITSMax[0] - FITSMin[0], 400.0);
202 if (binCount <= 0)
203 binCount = 400;
204
205 for (int n = 0; n < channels; n++)
206 {
207 intensity[n].fill(0, binCount);
208 frequency[n].fill(0, binCount);
209 cumulativeFrequency[n].fill(0, binCount);
210 binWidth[n] = (FITSMax[n] - FITSMin[n]) / (binCount - 1);
211 // Initialize the median to 0 in case the computation below fails.
212 imageData->setMedian(0, n);
213 }
214
215 QVector<QFuture<void>> futures;
216
217 for (int n = 0; n < channels; n++)
218 {
219 futures.append(QtConcurrent::run([ = ]()
220 {
221 for (int i = 0; i < binCount; i++)
222 intensity[n][i] = FITSMin[n] + (binWidth[n] * i);
223 }));
224 }
225
226 for (int n = 0; n < channels; n++)
227 {
228 futures.append(QtConcurrent::run([ = ]()
229 {
230 uint32_t offset = n * samples;
231
232 for (uint32_t i = 0; i < samples; i += sampleBy)
233 {
234 int32_t id = rint((buffer[i + offset] - FITSMin[n]) / binWidth[n]);
235 if (id < 0)
236 id = 0;
237 frequency[n][id] += sampleBy;
238 }
239 }));
240 }
241
242 for (QFuture<void> future : futures)
243 future.waitForFinished();
244
245 futures.clear();
246
247 for (int n = 0; n < channels; n++)
248 {
249 futures.append(QtConcurrent::run([ = ]()
250 {
251 uint32_t accumulator = 0;
252 for (int i = 0; i < binCount; i++)
253 {
254 accumulator += frequency[n][i];
255 cumulativeFrequency[n].replace(i, accumulator);
256 }
257 }));
258 }
259
260 for (QFuture<void> future : futures)
261 future.waitForFinished();
262
263 futures.clear();
264
265 for (int n = 0; n < channels; n++)
266 {
267 futures.append(QtConcurrent::run([ = ]()
268 {
269 double median[3] = {0};
270 const bool cutoffSpikes = ui->hideSaturated->isChecked();
271 const uint32_t halfCumulative = cumulativeFrequency[n][binCount - 1] / 2;
272
273 // Find which bin contains the median.
274 int median_bin = -1;
275 for (int i = 0; i < binCount; i++)
276 {
277 if (cumulativeFrequency[n][i] > halfCumulative)
278 {
279 median_bin = i;
280 break;
281 }
282 }
283
284 if (median_bin >= 0)
285 {
286 // The number of items in the median bin
287 const uint32_t median_bin_size = frequency[n][median_bin] / sampleBy;
288 if (median_bin_size > 0)
289 {
290 // The median is this element inside the sorted median_bin;
291 const uint32_t samples_before_median_bin = median_bin == 0 ? 0 : cumulativeFrequency[n][median_bin - 1];
293
297 {
298 // Fill a vector with the values in the median bin (sampled by sampleBy).
299 std::vector<T> median_bin_samples(median_bin_size);
300 uint32_t upto = 0;
301 const uint32_t offset = n * samples;
302 for (uint32_t i = 0; i < samples; i += sampleBy)
303 {
304 if (upto >= median_bin_size) break;
305 const int32_t id = rint((buffer[i + offset] - FITSMin[n]) / binWidth[n]);
306 if (id == median_bin)
307 median_bin_samples[upto++] = buffer[i + offset];
308 }
309 // Get the Nth value using N = the median position.
310 if (upto > 0)
311 {
313 std::nth_element(median_bin_samples.begin(), median_bin_samples.begin() + median_position,
314 median_bin_samples.begin() + upto);
316 }
317 }
318 }
319 }
320
321 imageData->setMedian(median[n], n);
322
323 if (cutoffSpikes)
324 {
325 QVector<double> sortedFreq = frequency[n];
326 std::sort(sortedFreq.begin(), sortedFreq.end());
327 double cutoff = sortedFreq[binCount * 0.99];
328 for (int i = 0; i < binCount; i++)
329 {
330 if (frequency[n][i] >= cutoff)
331 frequency[n][i] = cutoff;
332 }
333 }
334
335 }));
336 }
337
338 for (QFuture<void> future : futures)
339 future.waitForFinished();
340
341 // Custom index to indicate the overall contrast of the image
342 if (cumulativeFrequency[RED_CHANNEL][binCount / 4] > 0)
343 JMIndex = cumulativeFrequency[RED_CHANNEL][binCount / 8] / static_cast<double>(cumulativeFrequency[RED_CHANNEL][binCount /
344 4]);
345 else
346 JMIndex = 1;
347 qCDebug(KSTARS_FITS) << "FITHistogram: JMIndex " << JMIndex;
348
349 sliderTick.clear();
350 sliderScale.clear();
351 for (int n = 0; n < channels; n++)
352 {
353 sliderTick << fabs(FITSMax[n] - FITSMin[n]) / 99.0;
354 sliderScale << 99.0 / (FITSMax[n] - FITSMin[n] - sliderTick[n]);
355 }
356}
357
358void FITSHistogram::syncGUI()
359{
360 if (isGUISynced)
361 return;
362
363 const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
364 bool isColor = imageData->channels() > 1;
365 // R/K is always enabled
366 for (auto w : rgbWidgets[RED_CHANNEL])
367 w->setEnabled(true);
368 // G Channel
369 for (auto w : rgbWidgets[GREEN_CHANNEL])
371 // B Channel
372 for (auto w : rgbWidgets[BLUE_CHANNEL])
374
375 ui->meanEdit->setText(QString::number(imageData->getMean()));
376 ui->medianEdit->setText(QString::number(imageData->getMedian()));
377
378 for (int n = 0; n < imageData->channels(); n++)
379 {
380 double median = imageData->getMedian(n);
381
382 if (median > 100)
383 numDecimals << 0;
384 else if (median > 1)
385 numDecimals << 2;
386 else if (median > .01)
387 numDecimals << 4;
388 else if (median > .0001)
389 numDecimals << 6;
390 else
391 numDecimals << 10;
392
393 minBoxes[n]->setDecimals(numDecimals[n]);
394 minBoxes[n]->setSingleStep(fabs(FITSMax[n] - FITSMin[n]) / 20.0);
395 minBoxes[n]->setMinimum(FITSMin[n]);
396 minBoxes[n]->setMaximum(FITSMax[n] - sliderTick[n]);
397 minBoxes[n]->setValue(FITSMin[n] + (sliders[n]->minimumValue() / sliderScale[n]));
398
399 maxBoxes[n]->setDecimals(numDecimals[n]);
400 maxBoxes[n]->setSingleStep(fabs(FITSMax[n] - FITSMin[n]) / 20.0);
401 maxBoxes[n]->setMinimum(FITSMin[n] + sliderTick[n]);
402 maxBoxes[n]->setMaximum(FITSMax[n]);
403 maxBoxes[n]->setValue(FITSMin[n] + sliderTick[n] + (sliders[n]->maximumValue() / sliderScale[n]));
404 }
405
406 customPlot->clearGraphs();
407 graphs.clear();
408
409 for (int n = 0; n < imageData->channels(); n++)
410 {
411 graphs.append(customPlot->addGraph());
412 graphs[n]->setData(intensity[n], frequency[n]);
413 }
414
415 graphs[RED_CHANNEL]->setBrush(QBrush(QColor(170, 40, 80)));
416 graphs[RED_CHANNEL]->setPen(QPen(Qt::red));
417
418 if (isColor)
419 {
420 graphs[GREEN_CHANNEL]->setBrush(QBrush(QColor(80, 40, 170)));
421 graphs[GREEN_CHANNEL]->setPen(QPen(Qt::green));
422
423 graphs[BLUE_CHANNEL]->setBrush(QBrush(QColor(170, 40, 80)));
424 graphs[BLUE_CHANNEL]->setPen(QPen(Qt::blue));
425 }
426
427 customPlot->axisRect(0)->setRangeDrag(Qt::Horizontal);
428 customPlot->axisRect(0)->setRangeZoom(Qt::Horizontal);
429
430 customPlot->xAxis->setLabel(i18n("Intensity"));
431 customPlot->yAxis->setLabel(i18n("Frequency"));
432
433 // customPlot->xAxis->setRange(fits_min - ui->minEdit->singleStep(),
434 // fits_max + ui->maxEdit->singleStep());
435
436 customPlot->xAxis->rescale();
437 customPlot->yAxis->rescale();
438
439 customPlot->setInteraction(QCP::iRangeDrag, true);
440 customPlot->setInteraction(QCP::iRangeZoom, true);
441 customPlot->setInteraction(QCP::iSelectPlottables, true);
442
443 customPlot->replot();
444 resizePlot();
445
446 isGUISynced = true;
447}
448
449void FITSHistogram::resizePlot()
450{
451 if (!m_Constructed)
452 constructHistogram();
453
454 if (customPlot->width() < 300)
455 customPlot->yAxis->setTickLabels(false);
456 else
457 customPlot->yAxis->setTickLabels(true);
458 customPlot->xAxis->ticker()->setTickCount(customPlot->width() / 100);
459}
460
461double FITSHistogram::getJMIndex() const
462{
463 return JMIndex;
464}
465
466void FITSHistogram::applyScale()
467{
468 QVector<double> min, max;
469
470 min << minBoxes[0]->value() << minBoxes[1]->value() << minBoxes[2]->value();
471 max << maxBoxes[0]->value() << maxBoxes[1]->value() << maxBoxes[2]->value();
472
473 FITSHistogramCommand * histC;
474
475 if (ui->logR->isChecked())
476 type = FITS_LOG;
477 else
478 type = FITS_LINEAR;
479
480 histC = new FITSHistogramCommand(tab, this, type, min, max);
481
482 tab->getUndoStack()->push(histC);
483}
484
485void FITSHistogram::applyFilter(FITSScale ftype)
486{
487 QVector<double> min, max;
488
489 min.append(ui->minREdit->value());
490
491 FITSHistogramCommand * histC;
492
493 type = ftype;
494
495 histC = new FITSHistogramCommand(tab, this, type, min, max);
496
497 tab->getUndoStack()->push(histC);
498}
499
500QVector<uint32_t> FITSHistogram::getCumulativeFrequency(int channel) const
501{
502 return cumulativeFrequency[channel];
503}
504
505FITSHistogramCommand::FITSHistogramCommand(QWidget * parent,
506 FITSHistogram * inHisto,
507 FITSScale newType,
508 const QVector<double> &lmin,
509 const QVector<double> &lmax)
510{
511 tab = dynamic_cast<FITSTab *>(parent);
512 type = newType;
513 histogram = inHisto;
514 min = lmin;
515 max = lmax;
516}
517
518FITSHistogramCommand::~FITSHistogramCommand()
519{
520 delete[] delta;
521}
522
523bool FITSHistogramCommand::calculateDelta(const uint8_t * buffer)
524{
525 const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
526
527 uint8_t const * image_buffer = imageData->getImageBuffer();
528 int totalPixels =
529 imageData->width() * imageData->height() * imageData->channels();
530 unsigned long totalBytes = totalPixels * imageData->getBytesPerPixel();
531
532 auto * raw_delta = new uint8_t[totalBytes];
533
534 if (raw_delta == nullptr)
535 {
536 qWarning() << "Error! not enough memory to create image delta" << endl;
537 return false;
538 }
539
540 for (unsigned int i = 0; i < totalBytes; i++)
541 raw_delta[i] = buffer[i] ^ image_buffer[i];
542
543 compressedBytes = sizeof(uint8_t) * totalBytes + totalBytes / 64 + 16 + 3;
544 delete[] delta;
545 delta = new uint8_t[compressedBytes];
546
547 if (delta == nullptr)
548 {
549 delete[] raw_delta;
550 qCCritical(KSTARS_FITS)
551 << "FITSHistogram Error: Ran out of memory compressing delta";
552 return false;
553 }
554
555 int r = compress2(delta, &compressedBytes, raw_delta, totalBytes, 5);
556
557 if (r != Z_OK)
558 {
559 delete[] raw_delta;
560 /* this should NEVER happen */
561 qCCritical(KSTARS_FITS)
562 << "FITSHistogram Error: Failed to compress raw_delta";
563 return false;
564 }
565
566 // qDebug() << "compressed bytes size " << compressedBytes << " bytes" <<
567 // endl;
568
569 delete[] raw_delta;
570
571 return true;
572}
573
574bool FITSHistogramCommand::reverseDelta()
575{
576 FITSView * image = tab->getView();
577 const QSharedPointer<FITSData> &imageData = image->imageData();
578 uint8_t const * image_buffer = (imageData->getImageBuffer());
579
580 int totalPixels =
581 imageData->width() * imageData->height() * imageData->channels();
582 unsigned long totalBytes = totalPixels * imageData->getBytesPerPixel();
583
584 auto * output_image = new uint8_t[totalBytes];
585
586 if (output_image == nullptr)
587 {
588 qWarning() << "Error! not enough memory to create output image" << endl;
589 return false;
590 }
591
592 auto * raw_delta = new uint8_t[totalBytes];
593
594 if (raw_delta == nullptr)
595 {
596 delete[] output_image;
597 qWarning() << "Error! not enough memory to create image delta" << endl;
598 return false;
599 }
600
601 int r = uncompress(raw_delta, &totalBytes, delta, compressedBytes);
602 if (r != Z_OK)
603 {
604 qCCritical(KSTARS_FITS)
605 << "FITSHistogram compression error in reverseDelta()";
606 delete[] output_image;
607 delete[] raw_delta;
608 return false;
609 }
610
611 for (unsigned int i = 0; i < totalBytes; i++)
613
614 imageData->setImageBuffer(output_image);
615
616 delete[] raw_delta;
617
618 return true;
619}
620
621void FITSHistogramCommand::redo()
622{
623 FITSView * image = tab->getView();
624 const QSharedPointer<FITSData> &imageData = image->imageData();
625
626 uint8_t const * image_buffer = imageData->getImageBuffer();
627 uint8_t * buffer = nullptr;
628 unsigned int size =
629 imageData->width() * imageData->height() * imageData->channels();
630 int BBP = imageData->getBytesPerPixel();
631
633
634 if (delta != nullptr)
635 {
636 FITSImage::Statistic prevStats;
637 imageData->saveStatistics(prevStats);
638
639 reverseDelta();
640
641 imageData->restoreStatistics(stats);
642
643 stats = prevStats;
644 }
645 else
646 {
647 imageData->saveStatistics(stats);
648
649 // If it's rotation of flip, no need to calculate delta
650 if (type >= FITS_ROTATE_CW && type <= FITS_FLIP_V)
651 {
652 imageData->applyFilter(type);
653 }
654 else
655 {
656 buffer = new uint8_t[size * BBP];
657
658 if (buffer == nullptr)
659 {
660 qWarning()
661 << "Error! not enough memory to create image buffer in redo()"
662 << endl;
664 return;
665 }
666
667 memcpy(buffer, image_buffer, size * BBP);
668
669 QVector<double> dataMin = min, dataMax = max;
670 switch (type)
671 {
672 case FITS_AUTO:
673 case FITS_LINEAR:
674 imageData->applyFilter(FITS_LINEAR, nullptr, &dataMin, &dataMax);
675 break;
676
677 case FITS_LOG:
678 imageData->applyFilter(FITS_LOG, nullptr, &dataMin, &dataMax);
679 break;
680
681 case FITS_SQRT:
682 imageData->applyFilter(FITS_SQRT, nullptr, &dataMin, &dataMax);
683 break;
684
685 default:
686 imageData->applyFilter(type);
687 break;
688 }
689
690 calculateDelta(buffer);
691 delete[] buffer;
692 }
693 }
694
695 if (histogram != nullptr)
696 {
697 histogram->constructHistogram();
698
699 if (tab->getViewer()->isStarsMarked())
700 imageData->findStars().waitForFinished();
701 }
702
703 image->pushFilter(type);
704 image->rescale(ZOOM_KEEP_LEVEL);
705 image->updateFrame();
706
708}
709
710void FITSHistogramCommand::undo()
711{
712 FITSView * image = tab->getView();
713 const QSharedPointer<FITSData> &imageData = image->imageData();
714
716
717 if (delta != nullptr)
718 {
719 FITSImage::Statistic prevStats;
720 imageData->saveStatistics(prevStats);
721
722 reverseDelta();
723
724 imageData->restoreStatistics(stats);
725
726 stats = prevStats;
727 }
728 else
729 {
730 switch (type)
731 {
732 case FITS_ROTATE_CW:
733 imageData->applyFilter(FITS_ROTATE_CCW);
734 break;
735 case FITS_ROTATE_CCW:
736 imageData->applyFilter(FITS_ROTATE_CW);
737 break;
738 case FITS_FLIP_H:
739 case FITS_FLIP_V:
740 imageData->applyFilter(type);
741 break;
742 default:
743 break;
744 }
745 }
746
747 if (histogram != nullptr)
748 {
749 histogram->constructHistogram();
750
751 if (tab->getViewer()->isStarsMarked())
752 imageData->findStars().waitForFinished();
753 }
754
755 image->popFilter();
756 image->rescale(ZOOM_KEEP_LEVEL);
757 image->updateFrame();
758
760}
761
762QString FITSHistogramCommand::text() const
763{
764 switch (type)
765 {
766 case FITS_AUTO:
767 return i18n("Auto Scale");
768 case FITS_LINEAR:
769 return i18n("Linear Scale");
770 case FITS_LOG:
771 return i18n("Logarithmic Scale");
772 case FITS_SQRT:
773 return i18n("Square Root Scale");
774
775 default:
776 if (type - 1 <= FITSViewer::filterTypes.count())
777 return FITSViewer::filterTypes.at(type - 1);
778 break;
779 }
780
781 return i18n("Unknown");
782}
783
784void FITSHistogram::driftMouseOverLine(QMouseEvent * event)
785{
786 double intensity = customPlot->xAxis->pixelToCoord(event->localPos().x());
787
788 const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
789 uint8_t channels = imageData->channels();
790 QVector<double> freq(3, -1);
791
792 QVector<bool> inRange(3, false);
793 for (int n = 0; n < channels; n++)
794 {
795 if (intensity >= imageData->getMin(n) && intensity <= imageData->getMax(n))
796 inRange[n] = true;
797 }
798
799 if ( (channels == 1 && inRange[0] == false) || (!inRange[0] && !inRange[1] && !inRange[2]) )
800 {
802 return;
803 }
804
805 if (customPlot->xAxis->range().contains(intensity))
806 {
807 for (int n = 0; n < channels; n++)
808 {
809 int index = graphs[n]->findBegin(intensity, true);
810 freq[n] = graphs[n]->dataMainValue(index);
811 }
812
813 if (channels == 1 && freq[0] > 0)
814 {
816 event->globalPos(),
817 i18nc("Histogram tooltip; %1 is intensity; %2 is frequency;",
818 "<table>"
819 "<tr><td>Intensity: </td><td>%1</td></tr>"
820 "<tr><td>R Frequency: </td><td>%2</td></tr>"
821 "</table>",
822 QString::number(intensity, 'f', numDecimals[0]),
823 QString::number(freq[0], 'f', 0)));
824 }
825 else if (freq[1] > 0)
826 {
828 event->globalPos(),
829 i18nc("Histogram tooltip; %1 is intensity; %2 is frequency;",
830 "<table>"
831 "<tr><td>Intensity: </td><td>%1</td></tr>"
832 "<tr><td>R Frequency: </td><td>%2</td></tr>"
833 "<tr><td>G Frequency: </td><td>%3</td></tr>"
834 "<tr><td>B Frequency: </td><td>%4</td></tr>"
835 "</table>",
836 QString::number(intensity, 'f', numDecimals[0]),
837 QString::number(freq[0], 'f', 0),
838 QString::number(freq[1], 'f', 0),
839 QString::number(freq[2], 'f', 0)));
840 }
841 else
843
844 customPlot->replot();
845 }
846}
The FITSTab class holds information on the current view (drawing area) in addition to the undo/redo s...
Definition fitstab.h:46
void setRangeZoom(Qt::Orientations orientations)
void setRangeDrag(Qt::Orientations orientations)
void setTickLabels(bool show)
void setLabel(const QString &str)
void rescale(bool onlyVisiblePlottables=false)
double pixelToCoord(double value) const
QSharedPointer< QCPAxisTicker > ticker() const
QCPGraph * addGraph(QCPAxis *keyAxis=nullptr, QCPAxis *valueAxis=nullptr)
void setInteraction(const QCP::Interaction &interaction, bool enabled=true)
void mouseMove(QMouseEvent *event)
QCPAxis * xAxis
Q_SLOT void replot(QCustomPlot::RefreshPriority refreshPriority=QCustomPlot::rpRefreshHint)
QCPAxisRect * axisRect(int index=0) const
QCPAxis * yAxis
void minimumValueChanged(int min)
This signal is emitted when the slider minimum value has changed, with the new slider value as argume...
void maximumValueChanged(int max)
This signal is emitted when the slider maximum value has changed, with the new slider value as argume...
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
@ iRangeDrag
0x001 Axis ranges are draggable (see QCPAxisRect::setRangeDrag, QCPAxisRect::setRangeDragAxes)
@ iSelectPlottables
0x008 Plottables are selectable (e.g. graphs, curves, bars,... see QCPAbstractPlottable)
@ iRangeZoom
0x002 Axis ranges are zoomable with the mouse wheel (see QCPAxisRect::setRangeZoom,...
void clicked(bool checked)
void editingFinished()
void stateChanged(int state)
void restoreOverrideCursor()
void setOverrideCursor(const QCursor &cursor)
QString number(double n, char format, int precision)
WaitCursor
Horizontal
QTextStream & endl(QTextStream &stream)
QFuture< T > run(Function function,...)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void hideText()
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
void push(QUndoCommand *cmd)
void setEnabled(bool)
virtual bool event(QEvent *event) override
void resize(const QSize &)
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.