Kstars

fitshistogram.cpp
1 /*
2  SPDX-FileCopyrightText: 2015 Jasem Mutlaq <[email protected]>
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 
23 histogramUI::histogramUI(QDialog * parent) : QDialog(parent)
24 {
25  setupUi(parent);
26  setModal(false);
27 }
28 
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 
124 void FITSHistogram::showEvent(QShowEvent * event)
125 {
126  Q_UNUSED(event)
127  if (!m_Constructed)
128  constructHistogram();
129  syncGUI();
130 }
131 
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 
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 
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 
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 
461 double FITSHistogram::getJMIndex() const
462 {
463  return JMIndex;
464 }
465 
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 
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 
500 QVector<uint32_t> FITSHistogram::getCumulativeFrequency(int channel) const
501 {
502  return cumulativeFrequency[channel];
503 }
504 
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 
518 FITSHistogramCommand::~FITSHistogramCommand()
519 {
520  delete[] delta;
521 }
522 
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 
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 
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 
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 
710 void 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 
762 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 
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  {
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 }
void showText(const QPoint &pos, const QString &text, QWidget *w)
void maximumValueChanged(int max)
This signal is emitted when the slider maximum value has changed, with the new slider value as argume...
QFuture< T > run(Function function,...)
QTextStream & endl(QTextStream &stream)
QString number(int n, int base)
Type type(const QSqlDatabase &db)
QVector::iterator begin()
T value() const const
void append(const T &value)
void clicked(bool checked)
void stateChanged(int state)
T value(int i) const const
The FITSTab class holds information on the current view (drawing area) in addition to the undo/redo s...
Definition: fitstab.h:38
QString i18n(const char *text, const TYPE &arg...)
Horizontal
WaitCursor
@ iSelectPlottables
0x008 Plottables are selectable (e.g. graphs, curves, bars,... see QCPAbstractPlottable)
Definition: qcustomplot.h:258
const T & at(int i) const const
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
void waitForFinished()
QVector::iterator end()
void resize(int w, int h)
QVariant value
FITS Header Key.
Definition: fitsdata.h:87
void setOverrideCursor(const QCursor &cursor)
void editingFinished()
void hideText()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void mouseMove(QMouseEvent *event)
void restoreOverrideCursor()
void minimumValueChanged(int min)
This signal is emitted when the slider minimum value has changed, with the new slider value as argume...
@ iRangeZoom
0x002 Axis ranges are zoomable with the mouse wheel (see QCPAxisRect::setRangeZoom,...
Definition: qcustomplot.h:256
@ iRangeDrag
0x001 Axis ranges are draggable (see QCPAxisRect::setRangeDrag, QCPAxisRect::setRangeDragAxes)
Definition: qcustomplot.h:255
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Aug 11 2022 03:59:58 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.