7#include "fitshistogram.h"
15#include "fitsviewer.h"
19#include <QtConcurrent>
31 ui =
new histogramUI(
this);
32 tab =
dynamic_cast<FITSTab *
>(parent);
34 customPlot = ui->histogramPlot;
47 customPlot->xAxis->setTickLabelColor(
Qt::white);
48 customPlot->yAxis->setTickLabelColor(
Qt::white);
50 customPlot->xAxis->setLabelColor(
Qt::white);
51 customPlot->yAxis->setLabelColor(
Qt::white);
54 cumulativeFrequency.
resize(3);
63 rgbWidgets[RED_CHANNEL] << ui->RLabel << ui->minREdit << ui->redSlider
65 rgbWidgets[GREEN_CHANNEL] << ui->GLabel << ui->minGEdit << ui->greenSlider
67 rgbWidgets[BLUE_CHANNEL] << ui->BLabel << ui->minBEdit << ui->blueSlider
70 minBoxes << ui->minREdit << ui->minGEdit << ui->minBEdit;
71 maxBoxes << ui->maxREdit << ui->maxGEdit << ui->maxBEdit;
72 sliders << ui->redSlider << ui->greenSlider << ui->blueSlider;
78 customPlot->xAxis->grid()->setZeroLinePen(
Qt::NoPen);
79 customPlot->yAxis->grid()->setZeroLinePen(
Qt::NoPen);
90 &FITSHistogram::driftMouseOverLine);
92 for (
int i = 0; i < 3; i++)
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);
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);
114 qobject_cast<QDoubleSpinBox *>(w[1])->setValue(FITSMin[i] + (position / sliderScale[i]));
118 qobject_cast<QDoubleSpinBox *>(w[3])->setValue(FITSMin[i] + sliderTick[i] + (position / sliderScale[i]));
124void FITSHistogram::showEvent(
QShowEvent * event)
128 constructHistogram();
132void FITSHistogram::constructHistogram()
138 switch (imageData->getStatistics().dataType)
141 constructHistogram<uint8_t>();
145 constructHistogram<int16_t>();
149 constructHistogram<uint16_t>();
153 constructHistogram<int32_t>();
157 constructHistogram<uint32_t>();
161 constructHistogram<float>();
165 constructHistogram<int64_t>();
169 constructHistogram<double>();
176 m_Constructed =
true;
181template <
typename T>
void FITSHistogram::constructHistogram()
184 uint16_t
width = imageData->width(),
height = imageData->height();
185 uint8_t channels = imageData->channels();
187 auto *
const buffer =
reinterpret_cast<T
const *
>(imageData->getImageBuffer());
190 for (
int i = 0 ; i < 3; i++)
192 imageData->getMinMax(&min, &max, i);
198 const uint32_t sampleBy = samples > 1000000 ? samples / 1000000 : 1;
201 binCount = qMin(FITSMax[0] - FITSMin[0], 400.0);
205 for (
int n = 0; n < channels; n++)
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);
212 imageData->setMedian(0, n);
217 for (
int n = 0; n < channels; n++)
221 for (
int i = 0; i < binCount; i++)
222 intensity[n][i] = FITSMin[n] + (binWidth[n] * i);
226 for (
int n = 0; n < channels; n++)
230 uint32_t offset = n * samples;
232 for (uint32_t i = 0; i < samples; i += sampleBy)
234 int32_t
id = rint((buffer[i + offset] - FITSMin[n]) / binWidth[n]);
237 frequency[n][id] += sampleBy;
243 future.waitForFinished();
247 for (
int n = 0; n < channels; n++)
251 uint32_t accumulator = 0;
252 for (
int i = 0; i < binCount; i++)
254 accumulator += frequency[n][i];
255 cumulativeFrequency[n].replace(i, accumulator);
265 for (
int n = 0; n < channels; n++)
269 double median[3] = {0};
270 const bool cutoffSpikes = ui->hideSaturated->isChecked();
271 const uint32_t halfCumulative = cumulativeFrequency[n][binCount - 1] / 2;
275 for (
int i = 0; i < binCount; i++)
277 if (cumulativeFrequency[n][i] > halfCumulative)
287 const uint32_t median_bin_size = frequency[n][median_bin] / sampleBy;
288 if (median_bin_size > 0)
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;
294 if (median_position >= median_bin_size)
295 median_position = median_bin_size - 1;
296 if (median_position >= 0 && median_position < median_bin_size)
299 std::vector<T> median_bin_samples(median_bin_size);
301 const uint32_t offset = n * samples;
302 for (uint32_t i = 0; i < samples; i += sampleBy)
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];
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];
321 imageData->setMedian(median[n], n);
326 std::sort(sortedFreq.
begin(), sortedFreq.
end());
327 double cutoff = sortedFreq[binCount * 0.99];
328 for (
int i = 0; i < binCount; i++)
330 if (frequency[n][i] >= cutoff)
331 frequency[n][i] = cutoff;
342 if (cumulativeFrequency[RED_CHANNEL][binCount / 4] > 0)
343 JMIndex = cumulativeFrequency[RED_CHANNEL][binCount / 8] /
static_cast<double>(cumulativeFrequency[RED_CHANNEL][binCount /
347 qCDebug(KSTARS_FITS) <<
"FITHistogram: JMIndex " << JMIndex;
351 for (
int n = 0; n < channels; n++)
353 sliderTick << fabs(FITSMax[n] - FITSMin[n]) / 99.0;
354 sliderScale << 99.0 / (FITSMax[n] - FITSMin[n] - sliderTick[n]);
358void FITSHistogram::syncGUI()
364 bool isColor = imageData->channels() > 1;
366 for (
auto w : rgbWidgets[RED_CHANNEL])
369 for (
auto w : rgbWidgets[GREEN_CHANNEL])
370 w->setEnabled(isColor);
372 for (
auto w : rgbWidgets[BLUE_CHANNEL])
373 w->setEnabled(isColor);
378 for (
int n = 0; n < imageData->channels(); n++)
380 double median = imageData->getMedian(n);
386 else if (median > .01)
388 else if (median > .0001)
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]));
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]));
409 for (
int n = 0; n < imageData->channels(); n++)
411 graphs.append(customPlot->
addGraph());
412 graphs[n]->setData(intensity[n], frequency[n]);
415 graphs[RED_CHANNEL]->setBrush(
QBrush(
QColor(170, 40, 80)));
420 graphs[GREEN_CHANNEL]->setBrush(
QBrush(
QColor(80, 40, 170)));
423 graphs[BLUE_CHANNEL]->setBrush(
QBrush(
QColor(170, 40, 80)));
449void FITSHistogram::resizePlot()
452 constructHistogram();
454 if (customPlot->
width() < 300)
461double FITSHistogram::getJMIndex()
const
466void FITSHistogram::applyScale()
470 min << minBoxes[0]->value() << minBoxes[1]->value() << minBoxes[2]->value();
471 max << maxBoxes[0]->value() << maxBoxes[1]->value() << maxBoxes[2]->value();
473 FITSHistogramCommand * histC;
475 if (ui->logR->isChecked())
480 histC =
new FITSHistogramCommand(tab,
this, type, min, max);
482 tab->getUndoStack()->
push(histC);
485void FITSHistogram::applyFilter(FITSScale ftype)
489 min.
append(ui->minREdit->value());
491 FITSHistogramCommand * histC;
495 histC =
new FITSHistogramCommand(tab,
this, type, min, max);
497 tab->getUndoStack()->
push(histC);
502 return cumulativeFrequency[channel];
505FITSHistogramCommand::FITSHistogramCommand(
QWidget * parent,
506 FITSHistogram * inHisto,
511 tab =
dynamic_cast<FITSTab *
>(parent);
518FITSHistogramCommand::~FITSHistogramCommand()
523bool FITSHistogramCommand::calculateDelta(
const uint8_t * buffer)
527 uint8_t
const * image_buffer = imageData->getImageBuffer();
529 imageData->width() * imageData->height() * imageData->channels();
530 unsigned long totalBytes = totalPixels * imageData->getBytesPerPixel();
532 auto * raw_delta =
new uint8_t[totalBytes];
534 if (raw_delta ==
nullptr)
536 qWarning() <<
"Error! not enough memory to create image delta" <<
endl;
540 for (
unsigned int i = 0; i < totalBytes; i++)
541 raw_delta[i] = buffer[i] ^ image_buffer[i];
543 compressedBytes =
sizeof(uint8_t) * totalBytes + totalBytes / 64 + 16 + 3;
545 delta =
new uint8_t[compressedBytes];
547 if (delta ==
nullptr)
550 qCCritical(KSTARS_FITS)
551 <<
"FITSHistogram Error: Ran out of memory compressing delta";
555 int r = compress2(delta, &compressedBytes, raw_delta, totalBytes, 5);
561 qCCritical(KSTARS_FITS)
562 <<
"FITSHistogram Error: Failed to compress raw_delta";
574bool FITSHistogramCommand::reverseDelta()
576 FITSView * image = tab->getView();
578 uint8_t
const * image_buffer = (imageData->getImageBuffer());
581 imageData->width() * imageData->height() * imageData->channels();
582 unsigned long totalBytes = totalPixels * imageData->getBytesPerPixel();
584 auto * output_image =
new uint8_t[totalBytes];
586 if (output_image ==
nullptr)
588 qWarning() <<
"Error! not enough memory to create output image" <<
endl;
592 auto * raw_delta =
new uint8_t[totalBytes];
594 if (raw_delta ==
nullptr)
596 delete[] output_image;
597 qWarning() <<
"Error! not enough memory to create image delta" <<
endl;
601 int r = uncompress(raw_delta, &totalBytes, delta, compressedBytes);
604 qCCritical(KSTARS_FITS)
605 <<
"FITSHistogram compression error in reverseDelta()";
606 delete[] output_image;
611 for (
unsigned int i = 0; i < totalBytes; i++)
612 output_image[i] = raw_delta[i] ^ image_buffer[i];
614 imageData->setImageBuffer(output_image);
621void FITSHistogramCommand::redo()
623 FITSView * image = tab->getView();
626 uint8_t
const * image_buffer = imageData->getImageBuffer();
627 uint8_t * buffer =
nullptr;
629 imageData->width() * imageData->height() * imageData->channels();
630 int BBP = imageData->getBytesPerPixel();
634 if (delta !=
nullptr)
636 FITSImage::Statistic prevStats;
637 imageData->saveStatistics(prevStats);
641 imageData->restoreStatistics(stats);
647 imageData->saveStatistics(stats);
650 if (type >= FITS_ROTATE_CW && type <= FITS_FLIP_V)
652 imageData->applyFilter(type);
656 buffer =
new uint8_t[size * BBP];
658 if (buffer ==
nullptr)
661 <<
"Error! not enough memory to create image buffer in redo()"
667 memcpy(buffer, image_buffer, size * BBP);
674 imageData->applyFilter(FITS_LINEAR,
nullptr, &dataMin, &dataMax);
678 imageData->applyFilter(FITS_LOG,
nullptr, &dataMin, &dataMax);
682 imageData->applyFilter(FITS_SQRT,
nullptr, &dataMin, &dataMax);
686 imageData->applyFilter(type);
690 calculateDelta(buffer);
695 if (histogram !=
nullptr)
697 histogram->constructHistogram();
699 if (tab->getViewer()->isStarsMarked())
700 imageData->findStars().waitForFinished();
703 image->pushFilter(type);
704 image->rescale(ZOOM_KEEP_LEVEL);
705 image->updateFrame();
710void FITSHistogramCommand::undo()
712 FITSView * image = tab->getView();
717 if (delta !=
nullptr)
719 FITSImage::Statistic prevStats;
720 imageData->saveStatistics(prevStats);
724 imageData->restoreStatistics(stats);
733 imageData->applyFilter(FITS_ROTATE_CCW);
735 case FITS_ROTATE_CCW:
736 imageData->applyFilter(FITS_ROTATE_CW);
740 imageData->applyFilter(type);
747 if (histogram !=
nullptr)
749 histogram->constructHistogram();
751 if (tab->getViewer()->isStarsMarked())
752 imageData->findStars().waitForFinished();
756 image->rescale(ZOOM_KEEP_LEVEL);
757 image->updateFrame();
762QString FITSHistogramCommand::text()
const
767 return i18n(
"Auto Scale");
769 return i18n(
"Linear Scale");
771 return i18n(
"Logarithmic Scale");
773 return i18n(
"Square Root Scale");
776 if (type - 1 <= FITSViewer::filterTypes.count())
777 return FITSViewer::filterTypes.
at(type - 1);
781 return i18n(
"Unknown");
784void FITSHistogram::driftMouseOverLine(
QMouseEvent * event)
789 uint8_t channels = imageData->channels();
793 for (
int n = 0; n < channels; n++)
795 if (intensity >= imageData->getMin(n) && intensity <= imageData->getMax(n))
799 if ( (channels == 1 && inRange[0] ==
false) || (!inRange[0] && !inRange[1] && !inRange[2]) )
807 for (
int n = 0; n < channels; n++)
809 int index = graphs[n]->findBegin(intensity,
true);
810 freq[n] = graphs[n]->dataMainValue(index);
813 if (channels == 1 && freq[0] > 0)
817 i18nc(
"Histogram tooltip; %1 is intensity; %2 is frequency;",
819 "<tr><td>Intensity: </td><td>%1</td></tr>"
820 "<tr><td>R Frequency: </td><td>%2</td></tr>"
825 else if (freq[1] > 0)
829 i18nc(
"Histogram tooltip; %1 is intensity; %2 is frequency;",
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>"
The FITSTab class holds information on the current view (drawing area) in addition to the undo/redo s...
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
bool contains(double value) const
QCPGraph * addGraph(QCPAxis *keyAxis=nullptr, QCPAxis *valueAxis=nullptr)
void setInteraction(const QCP::Interaction &interaction, bool enabled=true)
void mouseMove(QMouseEvent *event)
Q_SLOT void replot(QCustomPlot::RefreshPriority refreshPriority=QCustomPlot::rpRefreshHint)
QCPAxisRect * axisRect(int index=0) const
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 stateChanged(int state)
void restoreOverrideCursor()
void setOverrideCursor(const QCursor &cursor)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
QString number(double n, char format, int precision)
QTextStream & endl(QTextStream &stream)
QFuture< T > run(Function function,...)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
void push(QUndoCommand *cmd)