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()
 
  134    const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
 
  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()
 
  183    const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
 
  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);
 
  215    QVector<QFuture<void>> futures;
 
  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;
 
  242    for (QFuture<void> future : futures)
 
  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);
 
  260    for (QFuture<void> future : futures)
 
  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);
 
  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++)
 
  330                    if (frequency[n][i] >= cutoff)
 
  331                        frequency[n][i] = cutoff;
 
  338    for (QFuture<void> future : futures)
 
  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()
 
  363    const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
 
  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]));
 
  406    customPlot->clearGraphs();
 
  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)));
 
  416    graphs[RED_CHANNEL]->setPen(QPen(
Qt::red));
 
  420        graphs[GREEN_CHANNEL]->setBrush(QBrush(QColor(80, 40, 170)));
 
  421        graphs[GREEN_CHANNEL]->setPen(QPen(
Qt::green));
 
  423        graphs[BLUE_CHANNEL]->setBrush(QBrush(QColor(170, 40, 80)));
 
  424        graphs[BLUE_CHANNEL]->setPen(QPen(
Qt::blue));
 
  430    customPlot->xAxis->setLabel(
i18n(
"Intensity"));
 
  431    customPlot->yAxis->setLabel(
i18n(
"Frequency"));
 
  436    customPlot->xAxis->rescale();
 
  437    customPlot->yAxis->rescale();
 
  443    customPlot->replot();
 
  449void FITSHistogram::resizePlot()
 
  452        constructHistogram();
 
  454    if (customPlot->width() < 300)
 
  455        customPlot->yAxis->setTickLabels(
false);
 
  457        customPlot->yAxis->setTickLabels(
true);
 
  458    customPlot->xAxis->ticker()->setTickCount(customPlot->width() / 100);
 
  461double FITSHistogram::getJMIndex()
 const 
  466void FITSHistogram::applyScale()
 
  468    QVector<double> min, max;
 
  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)
 
  487    QVector<double> min, max;
 
  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)
 
  525    const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
 
  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();
 
  577    const QSharedPointer<FITSData> &imageData = image->imageData();
 
  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();
 
  624    const QSharedPointer<FITSData> &imageData = image->imageData();
 
  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);
 
  669            QVector<double> dataMin = min, dataMax = max;
 
  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();
 
  713    const QSharedPointer<FITSData> &imageData = image->imageData();
 
  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)
 
  786    double intensity = customPlot->xAxis->pixelToCoord(
event->localPos().x());
 
  788    const QSharedPointer<FITSData> &imageData = tab->getView()->imageData();
 
  789    uint8_t channels = imageData->channels();
 
  790    QVector<double> freq(3, -1);
 
  792    QVector<bool> inRange(3, 
false);
 
  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]) )
 
  805    if (customPlot->xAxis->range().contains(intensity))
 
  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>" 
  844        customPlot->replot();
 
The FITSTab class holds information on the current view (drawing area) in addition to the undo/redo s...
 
void mouseMove(QMouseEvent *event)
 
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
 
T value(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)