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
histogramUI(QDialog * parent)23 histogramUI::histogramUI(QDialog * parent) : QDialog(parent)
24 {
25 setupUi(parent);
26 setModal(false);
27 }
28
FITSHistogram(QWidget * parent)29 FITSHistogram::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];
96 connect(qobject_cast<QDoubleSpinBox *>(w[1]), &QDoubleSpinBox::editingFinished, [this, i, w]()
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 });
103 connect(qobject_cast<QDoubleSpinBox *>(w[3]), &QDoubleSpinBox::editingFinished, [this, i, w]()
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
112 connect(qobject_cast<ctkRangeSlider *>(w[2]), &ctkRangeSlider::minimumValueChanged, [this, i, w](int position)
113 {
114 qobject_cast<QDoubleSpinBox *>(w[1])->setValue(FITSMin[i] + (position / sliderScale[i]));
115 });
116 connect(qobject_cast<ctkRangeSlider *>(w[2]), &ctkRangeSlider::maximumValueChanged, [this, i, w](int position)
117 {
118 qobject_cast<QDoubleSpinBox *>(w[3])->setValue(FITSMin[i] + sliderTick[i] + (position / sliderScale[i]));
119 });
120 }
121
122 }
123
showEvent(QShowEvent * event)124 void FITSHistogram::showEvent(QShowEvent * event)
125 {
126 Q_UNUSED(event)
127 if (!m_Constructed)
128 constructHistogram();
129 syncGUI();
130 }
131
constructHistogram()132 void 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:
141 constructHistogram<uint8_t>();
142 break;
143
144 case TSHORT:
145 constructHistogram<int16_t>();
146 break;
147
148 case TUSHORT:
149 constructHistogram<uint16_t>();
150 break;
151
152 case TLONG:
153 constructHistogram<int32_t>();
154 break;
155
156 case TULONG:
157 constructHistogram<uint32_t>();
158 break;
159
160 case TFLOAT:
161 constructHistogram<float>();
162 break;
163
164 case TLONGLONG:
165 constructHistogram<int64_t>();
166 break;
167
168 case TDOUBLE:
169 constructHistogram<double>();
170 break;
171
172 default:
173 break;
174 }
175
176 m_Constructed = true;
177 if (isVisible())
178 syncGUI();
179 }
180
constructHistogram()181 template <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];
292 uint32_t median_position = (halfCumulative - samples_before_median_bin) / sampleBy;
293
294 if (median_position >= median_bin_size)
295 median_position = median_bin_size - 1;
296 if (median_position >= 0 && median_position < median_bin_size)
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 {
312 if (median_position >= upto) median_position = upto - 1;
313 std::nth_element(median_bin_samples.begin(), median_bin_samples.begin() + median_position,
314 median_bin_samples.begin() + upto);
315 median[n] = median_bin_samples[median_position];
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
syncGUI()358 void 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])
370 w->setEnabled(isColor);
371 // B Channel
372 for (auto w : rgbWidgets[BLUE_CHANNEL])
373 w->setEnabled(isColor);
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
resizePlot()449 void 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
getJMIndex() const461 double FITSHistogram::getJMIndex() const
462 {
463 return JMIndex;
464 }
465
applyScale()466 void 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
applyFilter(FITSScale ftype)485 void 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
getCumulativeFrequency(int channel) const500 QVector<uint32_t> FITSHistogram::getCumulativeFrequency(int channel) const
501 {
502 return cumulativeFrequency[channel];
503 }
504
FITSHistogramCommand(QWidget * parent,FITSHistogram * inHisto,FITSScale newType,const QVector<double> & lmin,const QVector<double> & lmax)505 FITSHistogramCommand::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
~FITSHistogramCommand()518 FITSHistogramCommand::~FITSHistogramCommand()
519 {
520 delete[] delta;
521 }
522
calculateDelta(const uint8_t * buffer)523 bool 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
reverseDelta()574 bool 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++)
612 output_image[i] = raw_delta[i] ^ image_buffer[i];
613
614 imageData->setImageBuffer(output_image);
615
616 delete[] raw_delta;
617
618 return true;
619 }
620
redo()621 void 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
632 QApplication::setOverrideCursor(Qt::WaitCursor);
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;
663 QApplication::restoreOverrideCursor();
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
707 QApplication::restoreOverrideCursor();
708 }
709
undo()710 void FITSHistogramCommand::undo()
711 {
712 FITSView * image = tab->getView();
713 const QSharedPointer<FITSData> &imageData = image->imageData();
714
715 QApplication::setOverrideCursor(Qt::WaitCursor);
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
759 QApplication::restoreOverrideCursor();
760 }
761
text() const762 QString 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
driftMouseOverLine(QMouseEvent * event)784 void 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 {
801 QToolTip::hideText();
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 {
815 QToolTip::showText(
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 {
827 QToolTip::showText(
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
842 QToolTip::hideText();
843
844 customPlot->replot();
845 }
846 }
847