diff --git a/.arcconfig b/.arcconfig deleted file mode 100644 index 377c7ecb1..000000000 --- a/.arcconfig +++ /dev/null @@ -1,3 +0,0 @@ -{ - "phabricator.uri" : "https://phabricator.kde.org/" -} diff --git a/kstars/fitsviewer/fitshistogram.cpp b/kstars/fitsviewer/fitshistogram.cpp index 8e7d1de49..b1008a504 100644 --- a/kstars/fitsviewer/fitshistogram.cpp +++ b/kstars/fitsviewer/fitshistogram.cpp @@ -1,811 +1,860 @@ /* FITS Histogram Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "fitshistogram.h" #include "fits_debug.h" #include "fitsdata.h" #include "fitstab.h" #include "fitsview.h" #include "fitsviewer.h" #include "Options.h" #include #include histogramUI::histogramUI(QDialog *parent) : QDialog(parent) { setupUi(parent); setModal(false); } FITSHistogram::FITSHistogram(QWidget *parent) : QDialog(parent) { ui = new histogramUI(this); tab = dynamic_cast(parent); customPlot = ui->histogramPlot; customPlot->setBackground(QBrush(Qt::black)); customPlot->xAxis->setBasePen(QPen(Qt::white, 1)); customPlot->yAxis->setBasePen(QPen(Qt::white, 1)); customPlot->xAxis->setTickPen(QPen(Qt::white, 1)); customPlot->yAxis->setTickPen(QPen(Qt::white, 1)); customPlot->xAxis->setSubTickPen(QPen(Qt::white, 1)); customPlot->yAxis->setSubTickPen(QPen(Qt::white, 1)); customPlot->xAxis->setTickLabelColor(Qt::white); customPlot->yAxis->setTickLabelColor(Qt::white); customPlot->xAxis->setLabelColor(Qt::white); customPlot->yAxis->setLabelColor(Qt::white); customPlot->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); customPlot->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); customPlot->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); customPlot->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); customPlot->xAxis->grid()->setZeroLinePen(Qt::NoPen); customPlot->yAxis->grid()->setZeroLinePen(Qt::NoPen); r_graph = customPlot->addGraph(); r_graph->setBrush(QBrush(QColor(170, 40, 80))); r_graph->setPen(QPen(Qt::red)); //r_graph->setLineStyle(QCPGraph::lsImpulse); connect(ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(applyScale())); connect(customPlot, SIGNAL(mouseMove(QMouseEvent*)), this, SLOT(updateValues(QMouseEvent*))); connect(ui->minEdit, SIGNAL(valueChanged(double)), this, SLOT(updateLimits(double))); connect(ui->maxEdit, SIGNAL(valueChanged(double)), this, SLOT(updateLimits(double))); + connect(ui->minSlider, SIGNAL(valueChanged(int)), this, SLOT(updateSliders(int))); + connect(ui->maxSlider, SIGNAL(valueChanged(int)), this, SLOT(updateSliders(int))); connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), this, SLOT(checkRangeLimit(QCPRange))); } void FITSHistogram::showEvent(QShowEvent *event) { Q_UNUSED(event) syncGUI(); } void FITSHistogram::constructHistogram() { FITSData *image_data = tab->getView()->getImageData(); isGUISynced = false; switch (image_data->property("dataType").toInt()) { case TBYTE: constructHistogram(); break; case TSHORT: constructHistogram(); break; case TUSHORT: constructHistogram(); break; case TLONG: constructHistogram(); break; case TULONG: constructHistogram(); break; case TFLOAT: constructHistogram(); break; case TLONGLONG: constructHistogram(); break; case TDOUBLE: constructHistogram(); break; default: break; } if (isVisible()) syncGUI(); } template void FITSHistogram::constructHistogram() { FITSData *image_data = tab->getView()->getImageData(); uint16_t fits_w = image_data->width(), fits_h = image_data->height(); auto *buffer = reinterpret_cast(image_data->getImageBuffer()); image_data->getMinMax(&fits_min, &fits_max); uint32_t samples = fits_w * fits_h; binCount = sqrt(samples); intensity.fill(0, binCount); r_frequency.fill(0, binCount); cumulativeFrequency.fill(0, binCount); double pixel_range = fits_max - fits_min; binWidth = pixel_range / (binCount - 1); qCDebug(KSTARS_FITS) << "Histogram min:" << fits_min << ", max:" << fits_max << ", range:" << pixel_range << ", binW:" << binWidth << ", bin#:" << binCount; for (int i = 0; i < binCount; i++) intensity[i] = fits_min + (binWidth * i); uint16_t r_id = 0; if (image_data->channels() == 1) { for (uint32_t i = 0; i < samples; i += 4) { r_id = round((buffer[i] - fits_min) / binWidth); r_frequency[r_id >= binCount ? binCount - 1 : r_id] += 4; } } else { g_frequency.fill(0, binCount); b_frequency.fill(0, binCount); int g_offset = samples; int b_offset = samples * 2; for (uint32_t i = 0; i < samples; i += 4) { uint16_t g_id = 0, b_id = 0; r_id = round((buffer[i] - fits_min) / binWidth); r_frequency[r_id >= binCount ? binCount - 1 : r_id] += 4; g_id = round((buffer[i + g_offset] - fits_min) / binWidth); g_frequency[g_id >= binCount ? binCount - 1 : g_id] += 4; b_id = round((buffer[i + b_offset] - fits_min) / binWidth); b_frequency[b_id >= binCount ? binCount - 1 : b_id] += 4; } } // Cumulative Frequency int j = 0; double val = 0; for (int i = 0; i < binCount; i++) { val += r_frequency[j++]; cumulativeFrequency.replace(i, val); } if (image_data->channels() == 1) { for (int i = 0; i < binCount; i++) { if (r_frequency[i] > maxFrequency) maxFrequency = r_frequency[i]; } } else { for (int i = 0; i < binCount; i++) { if (r_frequency[i] > maxFrequency) maxFrequency = r_frequency[i]; if (g_frequency[i] > maxFrequency) maxFrequency = g_frequency[i]; if (b_frequency[i] > maxFrequency) maxFrequency = b_frequency[i]; } } double median = 0; int halfCumulative = cumulativeFrequency[binCount - 1] / 2; for (int i = 0; i < binCount; i++) { if (cumulativeFrequency[i] >= halfCumulative) { median = i * binWidth + fits_min; break; } } // Custom index to indicate the overall contrast of the image JMIndex = cumulativeFrequency[binCount / 8] / cumulativeFrequency[binCount / 4]; qCDebug(KSTARS_FITS) << "FITHistogram: JMIndex " << JMIndex; image_data->setMedian(median); } void FITSHistogram::syncGUI() { if (isGUISynced) return; FITSData *image_data = tab->getView()->getImageData(); + disconnect(ui->minEdit, SIGNAL(valueChanged(double)), this, SLOT(updateLimits(double))); + disconnect(ui->maxEdit, SIGNAL(valueChanged(double)), this, SLOT(updateLimits(double))); + disconnect(ui->minSlider, SIGNAL(valueChanged(int)), this, SLOT(updateSliders(int))); + disconnect(ui->maxSlider, SIGNAL(valueChanged(int)), this, SLOT(updateSliders(int))); + ui->meanEdit->setText(QString::number(image_data->getMean())); ui->medianEdit->setText(QString::number(image_data->getMedian())); - ui->minEdit->setMinimum(fits_min); - ui->minEdit->setMaximum(fits_max - 1); - ui->minEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); - ui->minEdit->setValue(fits_min); + if(!ui->minSlider->isSliderDown()) + { + ui->minEdit->setMinimum(fits_min); + ui->minEdit->setMaximum(fits_max - 1); + ui->minEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); + ui->minEdit->setValue(fits_min); - ui->maxEdit->setMinimum(fits_min + 1); - ui->maxEdit->setMaximum(fits_max); - ui->maxEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); - ui->maxEdit->setValue(fits_max); + + ui->minSlider->setMinimum(fits_min*10); + ui->minSlider->setMaximum((fits_max - 1)*10); + ui->minSlider->setSingleStep((fabs(fits_max - fits_min) / 20.0)*10); + ui->minSlider->setValue(fits_min*10); + } + + if(!ui->maxSlider->isSliderDown()) + { + ui->maxEdit->setMinimum(fits_min + 1); + ui->maxEdit->setMaximum(fits_max); + ui->maxEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); + ui->maxEdit->setValue(fits_max); + + + ui->maxSlider->setMinimum((fits_min + 1)*10); + ui->maxSlider->setMaximum(fits_max*10); + ui->maxSlider->setSingleStep((fabs(fits_max - fits_min) / 20.0)*10); + ui->maxSlider->setValue(fits_max*10); + } + + connect(ui->minEdit, SIGNAL(valueChanged(double)), this, SLOT(updateLimits(double))); + connect(ui->maxEdit, SIGNAL(valueChanged(double)), this, SLOT(updateLimits(double))); + connect(ui->minSlider, SIGNAL(valueChanged(int)), this, SLOT(updateSliders(int))); + connect(ui->maxSlider, SIGNAL(valueChanged(int)), this, SLOT(updateSliders(int))); r_graph->setData(intensity, r_frequency); if (image_data->channels() > 1) { g_graph = customPlot->addGraph(); b_graph = customPlot->addGraph(); g_graph->setBrush(QBrush(QColor(40, 170, 80))); b_graph->setBrush(QBrush(QColor(80, 40, 170))); g_graph->setPen(QPen(Qt::green)); b_graph->setPen(QPen(Qt::blue)); g_graph->setData(intensity, g_frequency); b_graph->setData(intensity, b_frequency); } customPlot->axisRect(0)->setRangeDrag(Qt::Horizontal); customPlot->axisRect(0)->setRangeZoom(Qt::Horizontal); customPlot->xAxis->setLabel(i18n("Intensity")); customPlot->yAxis->setLabel(i18n("Frequency")); customPlot->xAxis->setRange(fits_min, fits_max); if (maxFrequency > 0) customPlot->yAxis->rescale(); customPlot->setInteraction(QCP::iRangeDrag, true); customPlot->setInteraction(QCP::iRangeZoom, true); customPlot->setInteraction(QCP::iSelectPlottables, true); customPlot->replot(); resizePlot(); isGUISynced = true; } void FITSHistogram::resizePlot() { if(customPlot->width()<300) customPlot->yAxis->setTickLabels(false); else customPlot->yAxis->setTickLabels(true); customPlot->xAxis->ticker()->setTickCount(customPlot->width()/100); } #if 0 template void FITSHistogram::constructHistogram() { uint16_t fits_w = 0, fits_h = 0; FITSData *image_data = tab->getView()->getImageData(); T *buffer = reinterpret_cast(image_data->getImageBuffer()); image_data->getDimensions(&fits_w, &fits_h); image_data->getMinMax(&fits_min, &fits_max); uint32_t samples = fits_w * fits_h; binCount = sqrt(samples); intensity.fill(0, binCount); r_frequency.fill(0, binCount); cumulativeFrequency.fill(0, binCount); double pixel_range = fits_max - fits_min; binWidth = pixel_range / (binCount - 1); qCDebug(KSTARS_FITS) << "Histogram min:" << fits_min << ", max:" << fits_max << ", range:" << pixel_range << ", binW:" << binWidth << ", bin#:" << binCount; for (int i = 0; i < binCount; i++) intensity[i] = fits_min + (binWidth * i); uint16_t r_id = 0; if (image_data->getNumOfChannels() == 1) { for (uint32_t i = 0; i < samples; i += 4) { r_id = round((buffer[i] - fits_min) / binWidth); r_frequency[r_id >= binCount ? binCount - 1 : r_id] += 4; } } else { g_frequency.fill(0, binCount); b_frequency.fill(0, binCount); int g_offset = samples; int b_offset = samples * 2; for (uint32_t i = 0; i < samples; i += 4) { uint16_t g_id = 0, b_id = 0; r_id = round((buffer[i] - fits_min) / binWidth); r_frequency[r_id >= binCount ? binCount - 1 : r_id] += 4; g_id = round((buffer[i + g_offset] - fits_min) / binWidth); g_frequency[g_id >= binCount ? binCount - 1 : g_id] += 4; b_id = round((buffer[i + b_offset] - fits_min) / binWidth); b_frequency[b_id >= binCount ? binCount - 1 : b_id] += 4; } } // Cumulative Frequency for (int i = 0; i < binCount; i++) for (int j = 0; j <= i; j++) cumulativeFrequency[i] += r_frequency[j]; int maxFrequency = 0; if (image_data->getNumOfChannels() == 1) { for (int i = 0; i < binCount; i++) { if (r_frequency[i] > maxFrequency) maxFrequency = r_frequency[i]; } } else { for (int i = 0; i < binCount; i++) { if (r_frequency[i] > maxFrequency) maxFrequency = r_frequency[i]; if (g_frequency[i] > maxFrequency) maxFrequency = g_frequency[i]; if (b_frequency[i] > maxFrequency) maxFrequency = b_frequency[i]; } } double median = 0; int halfCumulative = cumulativeFrequency[binCount - 1] / 2; for (int i = 0; i < binCount; i++) { if (cumulativeFrequency[i] >= halfCumulative) { median = i * binWidth + fits_min; break; } } // Custom index to indicate the overall constrast of the image JMIndex = cumulativeFrequency[binCount / 8] / cumulativeFrequency[binCount / 4]; qCDebug(KSTARS_FITS) << "FITHistogram: JMIndex " << JMIndex; image_data->setMedian(median); ui->meanEdit->setText(QString::number(image_data->getMean())); ui->medianEdit->setText(QString::number(median)); ui->minEdit->setMinimum(fits_min); ui->minEdit->setMaximum(fits_max - 1); ui->minEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); ui->minEdit->setValue(fits_min); ui->maxEdit->setMinimum(fits_min + 1); ui->maxEdit->setMaximum(fits_max); ui->maxEdit->setSingleStep(fabs(fits_max - fits_min) / 20.0); ui->maxEdit->setValue(fits_max); r_graph->setData(intensity, r_frequency); if (image_data->getNumOfChannels() > 1) { g_graph = customPlot->addGraph(); b_graph = customPlot->addGraph(); g_graph->setBrush(QBrush(QColor(40, 170, 80))); b_graph->setBrush(QBrush(QColor(80, 40, 170))); g_graph->setPen(QPen(Qt::green)); b_graph->setPen(QPen(Qt::blue)); g_graph->setData(intensity, g_frequency); b_graph->setData(intensity, b_frequency); } customPlot->axisRect(0)->setRangeDrag(Qt::Horizontal); customPlot->axisRect(0)->setRangeZoom(Qt::Horizontal); customPlot->xAxis->setLabel(i18n("Intensity")); customPlot->yAxis->setLabel(i18n("Frequency")); customPlot->xAxis->setRange(fits_min, fits_max); if (maxFrequency > 0) customPlot->yAxis->setRange(0, maxFrequency); customPlot->setInteraction(QCP::iRangeDrag, true); customPlot->setInteraction(QCP::iRangeZoom, true); customPlot->setInteraction(QCP::iSelectPlottables, true); customPlot->replot(); } #endif void FITSHistogram::updateLimits(double value) { if (sender() == ui->minEdit) { if (value > ui->maxEdit->value()) ui->maxEdit->setValue(value + 1); } else if (sender() == ui->maxEdit) { if (value < ui->minEdit->value()) { ui->minEdit->setValue(value); ui->maxEdit->setValue(value + 1); } } } +void FITSHistogram::updateSliders(int value) +{ + if (sender() == ui->minSlider) + { + ui->minEdit->setValue(value/10.0); + if (value/10.0 > ui->maxEdit->value()) + ui->maxEdit->setValue(value/10.0 + 1); + } + else if (sender() == ui->maxSlider) + { + ui->maxEdit->setValue(value/10.0); + if (value/10.0 < ui->minEdit->value()) + { + ui->minEdit->setValue(value/10.0); + ui->maxEdit->setValue(value/10.0 + 1); + } + } + applyScale(); +} void FITSHistogram::checkRangeLimit(const QCPRange &range) { if (range.lower < fits_min) customPlot->xAxis->setRangeLower(fits_min); else if (range.upper > fits_max) customPlot->xAxis->setRangeUpper(fits_max); } double FITSHistogram::getJMIndex() const { return JMIndex; } void FITSHistogram::applyScale() { double min = ui->minEdit->value(); double max = ui->maxEdit->value(); FITSHistogramCommand *histC; if (ui->logR->isChecked()) type = FITS_LOG; else type = FITS_LINEAR; histC = new FITSHistogramCommand(tab, this, type, min, max); tab->getUndoStack()->push(histC); } void FITSHistogram::applyFilter(FITSScale ftype) { double min = ui->minEdit->value(); double max = ui->maxEdit->value(); FITSHistogramCommand *histC; type = ftype; histC = new FITSHistogramCommand(tab, this, type, min, max); tab->getUndoStack()->push(histC); } QVector FITSHistogram::getCumulativeFrequency() const { return cumulativeFrequency; } void FITSHistogram::updateValues(QMouseEvent *event) { int x = event->x(); double intensity_key = customPlot->xAxis->pixelToCoord(x); if (intensity_key < 0) return; double frequency_val = 0; for (int i = 0; i < binCount; i++) { if (intensity[i] > intensity_key) { frequency_val = r_frequency[i]; break; } } ui->intensityEdit->setText(QString::number(intensity_key)); ui->frequencyEdit->setText(QString::number(frequency_val)); } FITSHistogramCommand::FITSHistogramCommand(QWidget *parent, FITSHistogram *inHisto, FITSScale newType, double lmin, double lmax) { tab = dynamic_cast(parent); type = newType; histogram = inHisto; min = lmin; max = lmax; } FITSHistogramCommand::~FITSHistogramCommand() { delete[] delta; } bool FITSHistogramCommand::calculateDelta(const uint8_t *buffer) { FITSData *image_data = tab->getView()->getImageData(); uint8_t *image_buffer = image_data->getImageBuffer(); int totalPixels = image_data->width() * image_data->height() * image_data->channels(); unsigned long totalBytes = totalPixels * image_data->getBytesPerPixel(); auto *raw_delta = new uint8_t[totalBytes]; if (raw_delta == nullptr) { qWarning() << "Error! not enough memory to create image delta" << endl; return false; } for (unsigned int i = 0; i < totalBytes; i++) raw_delta[i] = buffer[i] ^ image_buffer[i]; compressedBytes = sizeof(uint8_t) * totalBytes + totalBytes / 64 + 16 + 3; delete[] delta; delta = new uint8_t[compressedBytes]; if (delta == nullptr) { delete[] raw_delta; qCCritical(KSTARS_FITS) << "FITSHistogram Error: Ran out of memory compressing delta"; return false; } int r = compress2(delta, &compressedBytes, raw_delta, totalBytes, 5); if (r != Z_OK) { delete[] raw_delta; /* this should NEVER happen */ qCCritical(KSTARS_FITS) << "FITSHistogram Error: Failed to compress raw_delta"; return false; } //qDebug() << "compressed bytes size " << compressedBytes << " bytes" << endl; delete[] raw_delta; return true; } bool FITSHistogramCommand::reverseDelta() { FITSView *image = tab->getView(); FITSData *image_data = image->getImageData(); uint8_t *image_buffer = (image_data->getImageBuffer()); int totalPixels = image_data->width() * image_data->height() * image_data->channels(); unsigned long totalBytes = totalPixels * image_data->getBytesPerPixel(); auto *output_image = new uint8_t[totalBytes]; if (output_image == nullptr) { qWarning() << "Error! not enough memory to create output image" << endl; return false; } auto *raw_delta = new uint8_t[totalBytes]; if (raw_delta == nullptr) { delete[] output_image; qWarning() << "Error! not enough memory to create image delta" << endl; return false; } int r = uncompress(raw_delta, &totalBytes, delta, compressedBytes); if (r != Z_OK) { qCCritical(KSTARS_FITS) << "FITSHistogram compression error in reverseDelta()"; delete[] output_image; delete[] raw_delta; return false; } for (unsigned int i = 0; i < totalBytes; i++) output_image[i] = raw_delta[i] ^ image_buffer[i]; image_data->setImageBuffer(output_image); delete[] raw_delta; return true; } void FITSHistogramCommand::redo() { FITSView *image = tab->getView(); FITSData *image_data = image->getImageData(); uint8_t *image_buffer = image_data->getImageBuffer(); uint8_t *buffer = nullptr; unsigned int size = image_data->width() * image_data->height() * image_data->channels(); int BBP = image_data->getBytesPerPixel(); QApplication::setOverrideCursor(Qt::WaitCursor); if (delta != nullptr) { FITSData::Statistic prevStats; image_data->saveStatistics(prevStats); reverseDelta(); image_data->restoreStatistics(stats); stats = prevStats; } else { image_data->saveStatistics(stats); // If it's rotation of flip, no need to calculate delta if (type >= FITS_ROTATE_CW && type <= FITS_FLIP_V) { image_data->applyFilter(type, image_buffer); } else { buffer = new uint8_t[size * BBP]; if (buffer == nullptr) { qWarning() << "Error! not enough memory to create image buffer in redo()" << endl; QApplication::restoreOverrideCursor(); return; } memcpy(buffer, image_buffer, size * BBP); double dataMin = min, dataMax = max; switch (type) { case FITS_AUTO: case FITS_LINEAR: image_data->applyFilter(FITS_LINEAR, nullptr, &dataMin, &dataMax); break; case FITS_LOG: image_data->applyFilter(FITS_LOG, nullptr, &dataMin, &dataMax); break; case FITS_SQRT: image_data->applyFilter(FITS_SQRT, nullptr, &dataMin, &dataMax); break; default: image_data->applyFilter(type); break; } calculateDelta(buffer); delete[] buffer; } } if (histogram != nullptr) { histogram->constructHistogram(); if (tab->getViewer()->isStarsMarked()) image_data->findStars(); } image->pushFilter(type); image->rescale(ZOOM_KEEP_LEVEL); image->updateFrame(); QApplication::restoreOverrideCursor(); } void FITSHistogramCommand::undo() { FITSView *image = tab->getView(); FITSData *image_data = image->getImageData(); QApplication::setOverrideCursor(Qt::WaitCursor); if (delta != nullptr) { FITSData::Statistic prevStats; image_data->saveStatistics(prevStats); reverseDelta(); image_data->restoreStatistics(stats); stats = prevStats; } else { switch (type) { case FITS_ROTATE_CW: image_data->applyFilter(FITS_ROTATE_CCW); break; case FITS_ROTATE_CCW: image_data->applyFilter(FITS_ROTATE_CW); break; case FITS_FLIP_H: case FITS_FLIP_V: image_data->applyFilter(type); break; default: break; } } if (histogram != nullptr) { histogram->constructHistogram(); if (tab->getViewer()->isStarsMarked()) image_data->findStars(); } image->popFilter(); image->rescale(ZOOM_KEEP_LEVEL); image->updateFrame(); QApplication::restoreOverrideCursor(); } QString FITSHistogramCommand::text() const { switch (type) { case FITS_AUTO: return i18n("Auto Scale"); break; case FITS_LINEAR: return i18n("Linear Scale"); break; case FITS_LOG: return i18n("Logarithmic Scale"); break; case FITS_SQRT: return i18n("Square Root Scale"); break; default: if (type - 1 <= FITSViewer::filterTypes.count()) return FITSViewer::filterTypes.at(type - 1); break; } return i18n("Unknown"); } diff --git a/kstars/fitsviewer/fitshistogram.h b/kstars/fitsviewer/fitshistogram.h index 7b168db98..21ca3b768 100644 --- a/kstars/fitsviewer/fitshistogram.h +++ b/kstars/fitsviewer/fitshistogram.h @@ -1,111 +1,112 @@ /* FITS Histogram Copyright (C) 2015 Jasem Mutlaq (mutlaqja@ikarustech.com) This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "fitscommon.h" #include "fitsdata.h" #include "ui_fitshistogramui.h" #include #include class QMouseEvent; class FITSTab; class histogramUI : public QDialog, public Ui::FITSHistogramUI { Q_OBJECT public: explicit histogramUI(QDialog *parent = nullptr); }; class FITSHistogram : public QDialog { Q_OBJECT friend class histDrawArea; public: explicit FITSHistogram(QWidget *parent); ~FITSHistogram() = default; void constructHistogram(); void syncGUI(); void applyFilter(FITSScale ftype); double getBinWidth() { return binWidth; } QVector getCumulativeFrequency() const; double getJMIndex() const; protected: void showEvent(QShowEvent *event); public slots: void applyScale(); void updateValues(QMouseEvent *event); void updateLimits(double value); + void updateSliders(int value); void checkRangeLimit(const QCPRange &range); void resizePlot(); private: template void constructHistogram(); histogramUI *ui { nullptr }; FITSTab *tab { nullptr }; QVector intensity; QVector r_frequency, g_frequency, b_frequency; QCPGraph *r_graph { nullptr }; QCPGraph *g_graph { nullptr }; QCPGraph *b_graph { nullptr }; QVector cumulativeFrequency; double binWidth { 0 }; double JMIndex { 0 }; double fits_min { 0 }; double fits_max { 0 }; uint16_t binCount { 0 }; int maxFrequency {0}; FITSScale type { FITS_AUTO }; bool isGUISynced { false}; QCustomPlot *customPlot { nullptr }; }; class FITSHistogramCommand : public QUndoCommand { public: FITSHistogramCommand(QWidget *parent, FITSHistogram *inHisto, FITSScale newType, double lmin, double lmax); virtual ~FITSHistogramCommand(); virtual void redo(); virtual void undo(); virtual QString text() const; private: bool calculateDelta(const uint8_t *buffer); bool reverseDelta(); FITSData::Statistic stats; FITSHistogram *histogram { nullptr }; FITSScale type; double min { 0 }; double max { 0 }; unsigned char *delta { nullptr }; unsigned long compressedBytes { 0 }; FITSTab *tab { nullptr }; }; diff --git a/kstars/fitsviewer/fitshistogramui.ui b/kstars/fitsviewer/fitshistogramui.ui index c0b8d5bf0..bd54ab424 100644 --- a/kstars/fitsviewer/fitshistogramui.ui +++ b/kstars/fitsviewer/fitshistogramui.ui @@ -1,237 +1,251 @@ FITSHistogramUI 0 0 212 528 0 0 0 0 16777215 16777215 Histogram true 4 4 4 4 0 0 0 200 16777215 16777215 Mean: Intensity: Min.: Frequency: 0 0 true 0 0 true Max: 0 0 true Median: 0 0 true + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + 0 0 Qt::LeftToRight false FITS Scale L&inear true &Log QDialogButtonBox::Apply|QDialogButtonBox::Close QCustomPlot QWidget
auxiliary/qcustomplot.h
1
diff --git a/kstars/fitsviewer/fitsview.cpp b/kstars/fitsviewer/fitsview.cpp index b8326cc1e..ed15fda23 100644 --- a/kstars/fitsviewer/fitsview.cpp +++ b/kstars/fitsviewer/fitsview.cpp @@ -1,1862 +1,1868 @@ /* FITS View Copyright (C) 2003-2017 Jasem Mutlaq Copyright (C) 2016-2017 Robert Lancaster This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "config-kstars.h" #include "fitsview.h" #include "fitsdata.h" #include "fitslabel.h" #include "kspopupmenu.h" #include "kstarsdata.h" #include "ksutils.h" #include "Options.h" #include "skymap.h" #include "fits_debug.h" #ifdef HAVE_INDI #include "basedevice.h" #include "indi/indilistener.h" #endif #include #include #include #include #include #include #include #define BASE_OFFSET 50 #define ZOOM_DEFAULT 100.0 #define ZOOM_MIN 10 #define ZOOM_MAX 400 #define ZOOM_LOW_INCR 10 #define ZOOM_HIGH_INCR 50 FITSView::FITSView(QWidget *parent, FITSMode fitsMode, FITSScale filterType) : QScrollArea(parent), zoomFactor(1.2) { grabGesture(Qt::PinchGesture); image_frame.reset(new FITSLabel(this)); filter = filterType; mode = fitsMode; setBackgroundRole(QPalette::Dark); markerCrosshair.setX(0); markerCrosshair.setY(0); setBaseSize(740, 530); connect(image_frame.get(), SIGNAL(newStatus(QString,FITSBar)), this, SIGNAL(newStatus(QString,FITSBar))); connect(image_frame.get(), SIGNAL(pointSelected(int,int)), this, SLOT(processPointSelection(int,int))); connect(image_frame.get(), SIGNAL(markerSelected(int,int)), this, SLOT(processMarkerSelection(int,int))); connect(&wcsWatcher, SIGNAL(finished()), this, SLOT(syncWCSState())); connect(&fitsWatcher, &QFutureWatcher::finished, this, &FITSView::loadInFrame); image_frame->setMouseTracking(true); setCursorMode( selectCursor); //This is the default mode because the Focus and Align FitsViews should not be in dragMouse mode noImageLabel = new QLabel(); noImage.load(":/images/noimage.png"); noImageLabel->setPixmap(noImage); noImageLabel->setAlignment(Qt::AlignCenter); this->setWidget(noImageLabel); redScopePixmap = QPixmap(":/icons/center_telescope_red.svg").scaled(32, 32, Qt::KeepAspectRatio, Qt::FastTransformation); magentaScopePixmap = QPixmap(":/icons/center_telescope_magenta.svg").scaled(32, 32, Qt::KeepAspectRatio, Qt::FastTransformation); //if (fitsMode == FITS_GUIDE) //connect(image_frame.get(), SIGNAL(pointSelected(int,int)), this, SLOT(processPointSelection(int,int))); // Default size //resize(INITIAL_W, INITIAL_H); } FITSView::~FITSView() { fitsWatcher.waitForFinished(); wcsWatcher.waitForFinished(); - + starProfileWidget->deleteLater(); delete (imageData); } /** This method looks at what mouse mode is currently selected and updates the cursor to match. */ void FITSView::updateMouseCursor() { if (cursorMode == dragCursor) { if (horizontalScrollBar()->maximum() > 0 || verticalScrollBar()->maximum() > 0) { if (!image_frame->getMouseButtonDown()) viewport()->setCursor(Qt::PointingHandCursor); else viewport()->setCursor(Qt::ClosedHandCursor); } else viewport()->setCursor(Qt::CrossCursor); } else if (cursorMode == selectCursor) { viewport()->setCursor(Qt::CrossCursor); } else if (cursorMode == scopeCursor) { viewport()->setCursor(QCursor(redScopePixmap, 10, 10)); } else if (cursorMode == crosshairCursor) { viewport()->setCursor(QCursor(magentaScopePixmap, 10, 10)); } } /** This is how the mouse mode gets set. The default for a FITSView in a FITSViewer should be the dragMouse The default for a FITSView in the Focus or Align module should be the selectMouse The different defaults are accomplished by putting making the actual default mouseMode the selectMouse, but when a FITSViewer loads an image, it immediately makes it the dragMouse. */ void FITSView::setCursorMode(CursorMode mode) { cursorMode = mode; updateMouseCursor(); if (mode == scopeCursor && imageHasWCS()) { if (!imageData->isWCSLoaded() && !wcsWatcher.isRunning()) { QFuture future = QtConcurrent::run(imageData, &FITSData::loadWCS); wcsWatcher.setFuture(future); } } } void FITSView::resizeEvent(QResizeEvent *event) { if ((imageData == nullptr) && noImageLabel != nullptr) { noImageLabel->setPixmap( noImage.scaled(width() - 20, height() - 20, Qt::KeepAspectRatio, Qt::FastTransformation)); noImageLabel->setFixedSize(width() - 5, height() - 5); } QScrollArea::resizeEvent(event); } #if 0 bool FITSView::loadFITS(const QString &inFilename, bool silent) { if (floatingToolBar != nullptr) { floatingToolBar->setVisible(true); } QProgressDialog fitsProg(this); bool setBayerParams = false; BayerParams param; if ((imageData != nullptr) && imageData->hasDebayer()) { setBayerParams = true; imageData->getBayerParams(¶m); } // In case loadWCS is still running for previous image data, let's wait until it's over wcsWatcher.waitForFinished(); delete imageData; imageData = nullptr; filterStack.clear(); filterStack.push(FITS_NONE); if (filter != FITS_NONE) filterStack.push(filter); imageData = new FITSData(mode); if (setBayerParams) imageData->setBayerParams(¶m); if (mode == FITS_NORMAL) { fitsProg.setWindowModality(Qt::WindowModal); fitsProg.setLabelText(i18n("Please hold while loading FITS file...")); fitsProg.setWindowTitle(i18n("Loading FITS")); fitsProg.setValue(10); qApp->processEvents(); } if (!imageData->loadFITS(inFilename, silent)) return false; if (mode == FITS_NORMAL) { if (fitsProg.wasCanceled()) return false; else { fitsProg.setValue(65); qApp->processEvents(); } } emit debayerToggled(imageData->hasDebayer()); currentWidth = imageData->width(); currentHeight = imageData->height(); image_width = currentWidth; image_height = currentHeight; image_frame->setSize(image_width, image_height); initDisplayImage(); // Rescale to fits window if (firstLoad) { currentZoom = 100; if (rescale(ZOOM_FIT_WINDOW) != 0) return false; firstLoad = false; } else { if (rescale(ZOOM_KEEP_LEVEL) != 0) return false; } if (mode == FITS_NORMAL) { if (fitsProg.wasCanceled()) return false; else { fitsProg.setValue(100); qApp->processEvents(); } } setAlignment(Qt::AlignCenter); // Load WCS data now if selected and image contains valid WCS header if (imageData->hasWCS() && Options::autoWCS() && (mode == FITS_NORMAL || mode == FITS_ALIGN) && !wcsWatcher.isRunning()) { QFuture future = QtConcurrent::run(imageData, &FITSData::loadWCS); wcsWatcher.setFuture(future); } else syncWCSState(); if (isVisible()) emit newStatus(QString("%1x%2").arg(image_width).arg(image_height), FITS_RESOLUTION); if (showStarProfile) { if(floatingToolBar != nullptr) toggleProfileAction->setChecked(true); QTimer::singleShot(100 , this , SLOT(viewStarProfile())); //Need to wait till the Focus module finds stars, if its the Focus module. } updateFrame(); emit imageLoaded(); return true; } #endif void FITSView::loadFITS(const QString &inFilename, bool silent) { if (floatingToolBar != nullptr) { floatingToolBar->setVisible(true); } bool setBayerParams = false; BayerParams param; if ((imageData != nullptr) && imageData->hasDebayer()) { setBayerParams = true; imageData->getBayerParams(¶m); } // In case loadWCS is still running for previous image data, let's wait until it's over wcsWatcher.waitForFinished(); delete imageData; imageData = nullptr; filterStack.clear(); filterStack.push(FITS_NONE); if (filter != FITS_NONE) filterStack.push(filter); imageData = new FITSData(mode); if (setBayerParams) imageData->setBayerParams(¶m); fitsWatcher.setFuture(imageData->loadFITS(inFilename, silent)); } void FITSView::loadInFrame() { // Check if the loading was OK if (fitsWatcher.result() == false) { m_LastError = imageData->getLastError(); emit failed(); return; } // Notify if there is debayer data. emit debayerToggled(imageData->hasDebayer()); // Set current width and height currentWidth = imageData->width(); currentHeight = imageData->height(); image_width = currentWidth; image_height = currentHeight; image_frame->setSize(image_width, image_height); // Init the display image initDisplayImage(); uint8_t *ASImageBuffer = nullptr; if (Options::autoStretch() && (filter == FITS_NONE || (filter >= FITS_ROTATE_CW && filter <= FITS_FLIP_V))) { // If we perform autostretch, we need to create a buffer to save the raw image data before // autostretch filter operation changes the data. // After rescaling is done, we uint32_t totalBytes = image_width * image_height *imageData->channels() * imageData->getBytesPerPixel(); ASImageBuffer = new uint8_t[totalBytes]; memcpy(ASImageBuffer, imageData->getImageBuffer(), totalBytes); imageData->applyFilter(FITS_AUTO_STRETCH); } else imageData->applyFilter(filter); // Rescale to fits window on first load if (firstLoad) { currentZoom = 100; if (rescale(ZOOM_FIT_WINDOW) == false) { m_LastError = i18n("Rescaling image failed."); delete [] ASImageBuffer; emit failed(); return; } firstLoad = false; } else { if (rescale(ZOOM_KEEP_LEVEL) == false) { m_LastError = i18n("Rescaling image failed."); delete [] ASImageBuffer; emit failed(); return; } } // Restore original raw buffer after Autostretch if applicable if (ASImageBuffer) { imageData->setImageBuffer(ASImageBuffer); } setAlignment(Qt::AlignCenter); // Load WCS data now if selected and image contains valid WCS header if (imageData->hasWCS() && Options::autoWCS() && (mode == FITS_NORMAL || mode == FITS_ALIGN) && !wcsWatcher.isRunning()) { QFuture future = QtConcurrent::run(imageData, &FITSData::loadWCS); wcsWatcher.setFuture(future); } else syncWCSState(); if (isVisible()) emit newStatus(QString("%1x%2").arg(image_width).arg(image_height), FITS_RESOLUTION); if (showStarProfile) { if(floatingToolBar != nullptr) toggleProfileAction->setChecked(true); //Need to wait till the Focus module finds stars, if its the Focus module. QTimer::singleShot(100 , this , SLOT(viewStarProfile())); } scaledImage = QImage(); updateFrame(); emit loaded(); } int FITSView::saveFITS(const QString &newFilename) { return imageData->saveFITS(newFilename); } bool FITSView::rescale(FITSZoom type) { switch (imageData->property("dataType").toInt()) { case TBYTE: return rescale(type); case TSHORT: return rescale(type); case TUSHORT: return rescale(type); case TLONG: return rescale(type); case TULONG: return rescale(type); case TFLOAT: return rescale(type); case TLONGLONG: return rescale(type); case TDOUBLE: return rescale(type); default: break; } return false; } FITSView::CursorMode FITSView::getCursorMode() { return cursorMode; } void FITSView::enterEvent(QEvent *event) { Q_UNUSED(event) if ((floatingToolBar != nullptr) && (imageData != nullptr)) { QPointer eff = new QGraphicsOpacityEffect(this); floatingToolBar->setGraphicsEffect(eff); QPointer a = new QPropertyAnimation(eff, "opacity"); a->setDuration(500); a->setStartValue(0.2); a->setEndValue(1); a->setEasingCurve(QEasingCurve::InBack); a->start(QPropertyAnimation::DeleteWhenStopped); } } void FITSView::leaveEvent(QEvent *event) { Q_UNUSED(event) if ((floatingToolBar != nullptr) && (imageData != nullptr)) { QPointer eff = new QGraphicsOpacityEffect(this); floatingToolBar->setGraphicsEffect(eff); QPointer a = new QPropertyAnimation(eff, "opacity"); a->setDuration(500); a->setStartValue(1); a->setEndValue(0.2); a->setEasingCurve(QEasingCurve::OutBack); a->start(QPropertyAnimation::DeleteWhenStopped); } } template bool FITSView::rescale(FITSZoom type) { if (rawImage.isNull()) return false; uint8_t *image_buffer = imageData->getImageBuffer(); uint32_t size = imageData->width() * imageData->height(); #if 0 int BBP= imageData->getBytesPerPixel(); filter = filterStack.last(); if (Options::autoStretch() && (filter == FITS_NONE || (filter >= FITS_ROTATE_CW && filter <= FITS_FLIP_V))) { image_buffer = new uint8_t[size * imageData->channels() * BBP]; memcpy(image_buffer, imageData->getImageBuffer(), size * imageData->channels() * BBP); displayBuffer = true; double data_min = -1; double data_max = -1; imageData->applyFilter(FITS_AUTO_STRETCH, image_buffer, &data_min, &data_max); min = data_min; max = data_max; } else { imageData->applyFilter(filter); imageData->getMinMax(&min, &max); } #endif scaledImage = QImage(); auto *buffer = reinterpret_cast(image_buffer); if (imageData->getMin(0) == imageData->getMax(0)) { rawImage.fill(Qt::white); emit newStatus(i18n("Image is saturated."), FITS_MESSAGE); } else { if (image_height != imageData->height() || image_width != imageData->width()) { image_width = imageData->width(); image_height = imageData->height(); initDisplayImage(); if (isVisible()) emit newStatus(QString("%1x%2").arg(image_width).arg(image_height), FITS_RESOLUTION); } image_frame->setScaledContents(true); currentWidth = rawImage.width(); currentHeight = rawImage.height(); if (imageData->channels() == 1) { double range = imageData->getMax(0) - imageData->getMin(0); double bscale = 255. / range; double bzero = (-imageData->getMin(0)) * (255. / range); QVector> futures; /* Fill in pixel values using indexed map, linear scale */ for (uint32_t j = 0; j < image_height; j++) { futures.append(QtConcurrent::run([=]() { T *runningBuffer = buffer +j*image_width; uint8_t *scanLine = rawImage.scanLine(j); for (uint32_t i = 0; i < image_width; i++) { //scanLine[i] = qBound(0, static_cast(runningBuffer[i] * bscale + bzero), 255); scanLine[i] = qBound(0.0, runningBuffer[i] * bscale + bzero, 255.0); } })); } for(QFuture future : futures) future.waitForFinished(); } else { QVector> futures; double bscaleR = 255. / (imageData->getMax(0) - imageData->getMin(0)); double bzeroR = (-imageData->getMin(0)) * (255. / (imageData->getMax(0) - imageData->getMin(0))); double bscaleG = 255. / (imageData->getMax(1) - imageData->getMin(1)); double bzeroG = (-imageData->getMin(1)) * (255. / (imageData->getMax(1) - imageData->getMin(1))); double bscaleB = 255. / (imageData->getMax(2) - imageData->getMin(2)); double bzeroB = (-imageData->getMin(2)) * (255. / (imageData->getMax(2) - imageData->getMin(2))); /* Fill in pixel values using indexed map, linear scale */ for (uint32_t j = 0; j < image_height; j++) { futures.append(QtConcurrent::run([=]() { auto *scanLine = reinterpret_cast((rawImage.scanLine(j))); T *runningBufferR = buffer + j*image_width; T *runningBufferG = buffer + j*image_width + size; T *runningBufferB = buffer + j*image_width + size*2; for (uint32_t i = 0; i < image_width; i++) { scanLine[i] = qRgb(runningBufferR[i] * bscaleR + bzeroR, runningBufferG[i] * bscaleG + bzeroG, runningBufferB[i] * bscaleB + bzeroB); } })); } for(QFuture future : futures) future.waitForFinished(); } #if 0 if (imageData->getNumOfChannels() == 1) { /* Fill in pixel values using indexed map, linear scale */ for (int j = 0; j < image_height; j++) { unsigned char *scanLine = display_image->scanLine(j); for (int i = 0; i < image_width; i++) { val = buffer[j * image_width + i] * bscale + bzero; scanLine[i] = qBound(0.0, val, 255.0); } } } else { double rval = 0, gval = 0, bval = 0; QRgb value; /* Fill in pixel values using indexed map, linear scale */ for (int j = 0; j < image_height; j++) { QRgb *scanLine = reinterpret_cast((display_image->scanLine(j))); for (int i = 0; i < image_width; i++) { rval = buffer[j * image_width + i]; gval = buffer[j * image_width + i + size]; bval = buffer[j * image_width + i + size * 2]; value = qRgb(rval * bscale + bzero, gval * bscale + bzero, bval * bscale + bzero); scanLine[i] = value; } } } #endif } switch (type) { case ZOOM_FIT_WINDOW: if ((rawImage.width() > width() || rawImage.height() > height())) { double w = baseSize().width() - BASE_OFFSET; double h = baseSize().height() - BASE_OFFSET; if (!firstLoad) { w = viewport()->rect().width() - BASE_OFFSET; h = viewport()->rect().height() - BASE_OFFSET; } // Find the zoom level which will enclose the current FITS in the current window size double zoomX = floor((w / static_cast(currentWidth)) * 100.); double zoomY = floor((h / static_cast(currentHeight)) * 100.); (zoomX < zoomY) ? currentZoom = zoomX : currentZoom = zoomY; currentWidth = image_width * (currentZoom / ZOOM_DEFAULT); currentHeight = image_height * (currentZoom / ZOOM_DEFAULT); if (currentZoom <= ZOOM_MIN) emit actionUpdated("view_zoom_out", false); } else { currentZoom = 100; currentWidth = image_width; currentHeight = image_height; } break; case ZOOM_KEEP_LEVEL: { currentWidth = image_width * (currentZoom / ZOOM_DEFAULT); currentHeight = image_height * (currentZoom / ZOOM_DEFAULT); } break; default: currentZoom = 100; break; } setWidget(image_frame.get()); if (type != ZOOM_KEEP_LEVEL) emit newStatus(QString("%1%").arg(currentZoom), FITS_ZOOM); return true; } void FITSView::ZoomIn() { if (currentZoom >= ZOOM_DEFAULT && Options::limitedResourcesMode()) { emit newStatus(i18n("Cannot zoom in further due to active limited resources mode."), FITS_MESSAGE); return; } if (currentZoom < ZOOM_DEFAULT) currentZoom += ZOOM_LOW_INCR; else currentZoom += ZOOM_HIGH_INCR; emit actionUpdated("view_zoom_out", true); if (currentZoom >= ZOOM_MAX) { currentZoom = ZOOM_MAX; emit actionUpdated("view_zoom_in", false); } currentWidth = image_width * (currentZoom / ZOOM_DEFAULT); currentHeight = image_height * (currentZoom / ZOOM_DEFAULT); updateFrame(); emit newStatus(QString("%1%").arg(currentZoom), FITS_ZOOM); } void FITSView::ZoomOut() { if (currentZoom <= ZOOM_DEFAULT) currentZoom -= ZOOM_LOW_INCR; else currentZoom -= ZOOM_HIGH_INCR; if (currentZoom <= ZOOM_MIN) { currentZoom = ZOOM_MIN; emit actionUpdated("view_zoom_out", false); } emit actionUpdated("view_zoom_in", true); currentWidth = image_width * (currentZoom / ZOOM_DEFAULT); currentHeight = image_height * (currentZoom / ZOOM_DEFAULT); updateFrame(); emit newStatus(QString("%1%").arg(currentZoom), FITS_ZOOM); } void FITSView::ZoomToFit() { if (rawImage.isNull() == false) { rescale(ZOOM_FIT_WINDOW); updateFrame(); } } void FITSView::updateFrame() { bool ok = false; if (currentZoom != ZOOM_DEFAULT) { // Only scale when necessary if (scaledImage.isNull() || currentWidth != lastWidth || currentHeight != lastHeight) { scaledImage = rawImage.scaled(currentWidth, currentHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); lastWidth = currentWidth; lastHeight = currentHeight; } ok = displayPixmap.convertFromImage(scaledImage); } else ok = displayPixmap.convertFromImage(rawImage); if (!ok) return; QPainter painter(&displayPixmap); drawOverlay(&painter); image_frame->setPixmap(displayPixmap); image_frame->resize(currentWidth, currentHeight); } void FITSView::ZoomDefault() { if (image_frame != nullptr) { emit actionUpdated("view_zoom_out", true); emit actionUpdated("view_zoom_in", true); currentZoom = ZOOM_DEFAULT; currentWidth = image_width; currentHeight = image_height; updateFrame(); emit newStatus(QString("%1%").arg(currentZoom), FITS_ZOOM); update(); } } void FITSView::drawOverlay(QPainter *painter) { painter->setRenderHint(QPainter::Antialiasing, Options::useAntialias()); if (markStars) drawStarCentroid(painter); if (trackingBoxEnabled && getCursorMode() != FITSView::scopeCursor) drawTrackingBox(painter); if (!markerCrosshair.isNull()) drawMarker(painter); if (showCrosshair) drawCrosshair(painter); if (showObjects) drawObjectNames(painter); if (showEQGrid) drawEQGrid(painter); if (showPixelGrid) drawPixelGrid(painter); } void FITSView::updateMode(FITSMode fmode) { mode = fmode; } void FITSView::drawMarker(QPainter *painter) { painter->setPen(QPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")), 2)); painter->setBrush(Qt::NoBrush); float pxperdegree = (currentZoom / ZOOM_DEFAULT) * (57.3 / 1.8); float s1 = 0.5 * pxperdegree; float s2 = pxperdegree; float s3 = 2.0 * pxperdegree; float x0 = markerCrosshair.x() * (currentZoom / ZOOM_DEFAULT); float y0 = markerCrosshair.y() * (currentZoom / ZOOM_DEFAULT); float x1 = x0 - 0.5 * s1; float y1 = y0 - 0.5 * s1; float x2 = x0 - 0.5 * s2; float y2 = y0 - 0.5 * s2; float x3 = x0 - 0.5 * s3; float y3 = y0 - 0.5 * s3; //Draw radial lines painter->drawLine(QPointF(x1, y0), QPointF(x3, y0)); painter->drawLine(QPointF(x0 + s2, y0), QPointF(x0 + 0.5 * s1, y0)); painter->drawLine(QPointF(x0, y1), QPointF(x0, y3)); painter->drawLine(QPointF(x0, y0 + 0.5 * s1), QPointF(x0, y0 + s2)); //Draw circles at 0.5 & 1 degrees painter->drawEllipse(QRectF(x1, y1, s1, s1)); painter->drawEllipse(QRectF(x2, y2, s2, s2)); } void FITSView::drawStarCentroid(QPainter *painter) { painter->setPen(QPen(Qt::red, 2)); // image_data->getStarCenter(); QList starCenters = imageData->getStarCenters(); for (int i = 0; i < starCenters.count(); i++) { int x1 = (starCenters[i]->x - starCenters[i]->width / 2) * (currentZoom / ZOOM_DEFAULT); int y1 = (starCenters[i]->y - starCenters[i]->width / 2) * (currentZoom / ZOOM_DEFAULT); int w = (starCenters[i]->width) * (currentZoom / ZOOM_DEFAULT); painter->drawEllipse(x1, y1, w, w); } } void FITSView::drawTrackingBox(QPainter *painter) { painter->setPen(QPen(Qt::green, 2)); if (trackingBox.isNull()) return; int x1 = trackingBox.x() * (currentZoom / ZOOM_DEFAULT); int y1 = trackingBox.y() * (currentZoom / ZOOM_DEFAULT); int w = trackingBox.width() * (currentZoom / ZOOM_DEFAULT); int h = trackingBox.height() * (currentZoom / ZOOM_DEFAULT); painter->drawRect(x1, y1, w, h); } /** This Method draws a large Crosshair in the center of the image, it is like a set of axes. */ void FITSView::drawCrosshair(QPainter *painter) { float scale = (currentZoom / ZOOM_DEFAULT); QPointF c = QPointF((qreal)image_width / 2 * scale, (qreal)image_height / 2 * scale); float midX = (float)image_width / 2 * scale; float midY = (float)image_height / 2 * scale; float maxX = (float)image_width * scale; float maxY = (float)image_height * scale; float r = 50 * scale; painter->setPen(QPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")))); //Horizontal Line to Circle painter->drawLine(0, midY, midX - r, midY); //Horizontal Line past Circle painter->drawLine(midX + r, midY, maxX, midY); //Vertical Line to Circle painter->drawLine(midX, 0, midX, midY - r); //Vertical Line past Circle painter->drawLine(midX, midY + r, midX, maxY); //Circles painter->drawEllipse(c, r, r); painter->drawEllipse(c, r / 2, r / 2); } /** This method is intended to draw a pixel grid onto the image. It first determines useful information from the image. Then it draws the axes on the image if the crosshairs are not displayed. Finally it draws the gridlines so that there will be 4 Gridlines on either side of the axes. Note: This has to start drawing at the center not at the edges because the center axes must be in the center of the image. */ void FITSView::drawPixelGrid(QPainter *painter) { float scale = (currentZoom / ZOOM_DEFAULT); double width = image_width * scale; double height = image_height * scale; double cX = width / 2; double cY = height / 2; double deltaX = width / 10; double deltaY = height / 10; //draw the Axes painter->setPen(QPen(Qt::red)); painter->drawText(cX - 30, height - 5, QString::number((int)((cX) / scale))); painter->drawText(width - 30, cY - 5, QString::number((int)((cY) / scale))); if (!showCrosshair) { painter->drawLine(cX, 0, cX, height); painter->drawLine(0, cY, width, cY); } painter->setPen(QPen(Qt::gray)); //Start one iteration past the Center and draw 4 lines on either side of 0 for (int x = deltaX; x < cX - deltaX; x += deltaX) { painter->drawText(cX + x - 30, height - 5, QString::number((int)((cX + x) / scale))); painter->drawText(cX - x - 30, height - 5, QString::number((int)((cX - x) / scale))); painter->drawLine(cX - x, 0, cX - x, height); painter->drawLine(cX + x, 0, cX + x, height); } //Start one iteration past the Center and draw 4 lines on either side of 0 for (int y = deltaY; y < cY - deltaY; y += deltaY) { painter->drawText(width - 30, cY + y - 5, QString::number((int)((cY + y) / scale))); painter->drawText(width - 30, cY - y - 5, QString::number((int)((cY - y) / scale))); painter->drawLine(0, cY + y, width, cY + y); painter->drawLine(0, cY - y, width, cY - y); } } bool FITSView::imageHasWCS() { if (imageData != nullptr) return imageData->hasWCS(); return false; } void FITSView::drawObjectNames(QPainter *painter) { painter->setPen(QPen(QColor(KStarsData::Instance()->colorScheme()->colorNamed("FITSObjectLabelColor")))); float scale = (currentZoom / ZOOM_DEFAULT); foreach (FITSSkyObject *listObject, imageData->getSkyObjects()) { painter->drawRect(listObject->x() * scale - 5, listObject->y() * scale - 5, 10, 10); painter->drawText(listObject->x() * scale + 10, listObject->y() * scale + 10, listObject->skyObject()->name()); } } /** This method will paint EQ Gridlines in an overlay if there is WCS data present. It determines the minimum and maximum RA and DEC, then it uses that information to judge which gridLines to draw. Then it calls the drawEQGridlines methods below to draw gridlines at those specific RA and Dec values. */ void FITSView::drawEQGrid(QPainter *painter) { float scale = (currentZoom / ZOOM_DEFAULT); if (imageData->hasWCS()) { wcs_point *wcs_coord = imageData->getWCSCoord(); if (wcs_coord != nullptr) { int size = image_width * image_height; double maxRA = -1000; double minRA = 1000; double maxDec = -1000; double minDec = 1000; for (int i = 0; i < (size); i++) { double ra = wcs_coord[i].ra; double dec = wcs_coord[i].dec; if (ra > maxRA) maxRA = ra; if (ra < minRA) minRA = ra; if (dec > maxDec) maxDec = dec; if (dec < minDec) minDec = dec; } auto minDecMinutes = (int)(minDec * 12); //This will force the Dec Scale to 5 arc minutes in the loop auto maxDecMinutes = (int)(maxDec * 12); auto minRAMinutes = (int)(minRA / 15.0 * 120.0); //This will force the scale to 1/2 minutes of RA in the loop from 0 to 50 degrees auto maxRAMinutes = (int)(maxRA / 15.0 * 120.0); double raConvert = 15 / 120.0; //This will undo the calculation above to retrieve the actual RA. double decConvert = 1.0 / 12.0; //This will undo the calculation above to retrieve the actual DEC. if (maxDec > 50 || minDec < -50) { minRAMinutes = (int)(minRA / 15.0 * 60.0); //This will force the scale to 1 min of RA from 50 to 80 degrees maxRAMinutes = (int)(maxRA / 15.0 * 60.0); raConvert = 15 / 60.0; } if (maxDec > 80 || minDec < -80) { minRAMinutes = (int)(minRA / 15.0 * 30); //This will force the scale to 2 min of RA from 80 to 85 degrees maxRAMinutes = (int)(maxRA / 15.0 * 30); raConvert = 15 / 30.0; } if (maxDec > 85 || minDec < -85) { minRAMinutes = (int)(minRA / 15.0 * 6); //This will force the scale to 10 min of RA from 85 to 89 degrees maxRAMinutes = (int)(maxRA / 15.0 * 6); raConvert = 15 / 6.0; } if (maxDec >= 89.25 || minDec <= -89.25) { minRAMinutes = (int)(minRA / 15); //This will force the scale to whole hours of RA in the loop really close to the poles maxRAMinutes = (int)(maxRA / 15); raConvert = 15; } painter->setPen(QPen(Qt::yellow)); QPointF pixelPoint, imagePoint, pPoint; //This section draws the RA Gridlines for (int targetRA = minRAMinutes; targetRA <= maxRAMinutes; targetRA++) { painter->setPen(QPen(Qt::yellow)); double target = targetRA * raConvert; if (eqGridPoints.count() != 0) eqGridPoints.clear(); double increment = std::abs((maxDec - minDec) / 100.0); //This will determine how many points to use to create the RA Line for (double targetDec = minDec; targetDec <= maxDec; targetDec += increment) { SkyPoint pointToGet(target / 15.0, targetDec); bool inImage = imageData->wcsToPixel(pointToGet, pixelPoint, imagePoint); if (inImage) { QPointF pt(pixelPoint.x() * scale, pixelPoint.y() * scale); eqGridPoints.append(pt); } } if (eqGridPoints.count() > 1) { for (int i = 1; i < eqGridPoints.count(); i++) painter->drawLine(eqGridPoints.value(i - 1), eqGridPoints.value(i)); QPointF pt = getPointForGridLabel(); if (pt.x() != -100) { if (maxDec > 50 || maxDec < -50) painter->drawText(pt.x(), pt.y(), QString::number(dms(target).hour()) + "h " + QString::number(dms(target).minute()) + '\''); else painter->drawText(pt.x() - 20, pt.y(), QString::number(dms(target).hour()) + "h " + QString::number(dms(target).minute()) + "' " + QString::number(dms(target).second()) + "''"); } } } //This section draws the DEC Gridlines for (int targetDec = minDecMinutes; targetDec <= maxDecMinutes; targetDec++) { if (eqGridPoints.count() != 0) eqGridPoints.clear(); double increment = std::abs((maxRA - minRA) / 100.0); //This will determine how many points to use to create the Dec Line double target = targetDec * decConvert; for (double targetRA = minRA; targetRA <= maxRA; targetRA += increment) { SkyPoint pointToGet(targetRA / 15, targetDec * decConvert); bool inImage = imageData->wcsToPixel(pointToGet, pixelPoint, imagePoint); if (inImage) { QPointF pt(pixelPoint.x() * scale, pixelPoint.y() * scale); eqGridPoints.append(pt); } } if (eqGridPoints.count() > 1) { for (int i = 1; i < eqGridPoints.count(); i++) painter->drawLine(eqGridPoints.value(i - 1), eqGridPoints.value(i)); QPointF pt = getPointForGridLabel(); if (pt.x() != -100) painter->drawText(pt.x(), pt.y(), QString::number(dms(target).degree()) + "° " + QString::number(dms(target).arcmin()) + '\''); } } //This Section Draws the North Celestial Pole if present SkyPoint NCP(0, 90); bool NCPtest = imageData->wcsToPixel(NCP, pPoint, imagePoint); if (NCPtest) { bool NCPinImage = (pPoint.x() > 0 && pPoint.x() < image_width) && (pPoint.y() > 0 && pPoint.y() < image_height); if (NCPinImage) { painter->fillRect(pPoint.x() * scale - 2, pPoint.y() * scale - 2, 4, 4, KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")); painter->drawText(pPoint.x() * scale + 15, pPoint.y() * scale + 15, i18nc("North Celestial Pole", "NCP")); } } //This Section Draws the South Celestial Pole if present SkyPoint SCP(0, -90); bool SCPtest = imageData->wcsToPixel(SCP, pPoint, imagePoint); if (SCPtest) { bool SCPinImage = (pPoint.x() > 0 && pPoint.x() < image_width) && (pPoint.y() > 0 && pPoint.y() < image_height); if (SCPinImage) { painter->fillRect(pPoint.x() * scale - 2, pPoint.y() * scale - 2, 4, 4, KStarsData::Instance()->colorScheme()->colorNamed("TargetColor")); painter->drawText(pPoint.x() * scale + 15, pPoint.y() * scale + 15, i18nc("South Celestial Pole", "SCP")); } } } } } bool FITSView::pointIsInImage(QPointF pt, bool scaled) { float scale = (currentZoom / ZOOM_DEFAULT); if (scaled) return pt.x() < image_width * scale && pt.y() < image_height * scale && pt.x() > 0 && pt.y() > 0; else return pt.x() < image_width && pt.y() < image_height && pt.x() > 0 && pt.y() > 0; } QPointF FITSView::getPointForGridLabel() { float scale = (currentZoom / ZOOM_DEFAULT); //These get the maximum X and Y points in the list that are in the image QPointF maxXPt(image_width * scale / 2, image_height * scale / 2); for (auto& p : eqGridPoints) { if (p.x() > maxXPt.x() && pointIsInImage(p, true)) maxXPt = p; } QPointF maxYPt(image_width * scale / 2, image_height * scale / 2); for (auto& p : eqGridPoints) { if (p.y() > maxYPt.y() && pointIsInImage(p, true)) maxYPt = p; } QPointF minXPt(image_width * scale / 2, image_height * scale / 2); for (auto& p : eqGridPoints) { if (p.x() < minXPt.x() && pointIsInImage(p, true)) minXPt = p; } QPointF minYPt(image_width * scale / 2, image_height * scale / 2); for (auto& p : eqGridPoints) { if (p.y() < minYPt.y() && pointIsInImage(p, true)) minYPt = p; } //This gives preference to points that are on the right hand side and bottom. //But if the line doesn't intersect the right or bottom, it then tries for the top and left. //If no points are found in the image, it returns a point off the screen //If all else fails, like in the case of a circle on the image, it returns the far right point. if (image_width * scale - maxXPt.x() < 10) { return QPointF( image_width * scale - 50, maxXPt.y() - 10); //This will draw the text on the right hand side, up and to the left of the point where the line intersects } if (image_height * scale - maxYPt.y() < 10) return QPointF( maxYPt.x() - 40, image_height * scale - 10); //This will draw the text on the bottom side, up and to the left of the point where the line intersects if (minYPt.y() * scale < 30) return QPointF( minYPt.x() + 10, 20); //This will draw the text on the top side, down and to the right of the point where the line intersects if (minXPt.x() * scale < 30) return QPointF( 10, minXPt.y() + 20); //This will draw the text on the left hand side, down and to the right of the point where the line intersects if (maxXPt.x() == image_width * scale / 2 && maxXPt.y() == image_height * scale / 2) return QPointF(-100, -100); //All of the points were off the screen return QPoint(maxXPt.x() - 40, maxXPt.y() - 10); } void FITSView::setFirstLoad(bool value) { firstLoad = value; } QPixmap &FITSView::getTrackingBoxPixmap(uint8_t margin) { if (trackingBox.isNull()) return trackingBoxPixmap; int x1 = (trackingBox.x() - margin) * (currentZoom / ZOOM_DEFAULT); int y1 = (trackingBox.y() - margin) * (currentZoom / ZOOM_DEFAULT); int w = (trackingBox.width() + margin*2) * (currentZoom / ZOOM_DEFAULT); int h = (trackingBox.height() + margin*2) * (currentZoom / ZOOM_DEFAULT); trackingBoxPixmap = image_frame->grab(QRect(x1, y1, w, h)); return trackingBoxPixmap; } void FITSView::setTrackingBox(const QRect &rect) { if (rect != trackingBox) { trackingBox = rect; updateFrame(); if(showStarProfile) viewStarProfile(); } } void FITSView::resizeTrackingBox(int newSize) { int x = trackingBox.x() + trackingBox.width()/2; int y = trackingBox.y() + trackingBox.height()/2; int delta = newSize / 2; setTrackingBox(QRect( x - delta, y - delta, newSize, newSize)); } bool FITSView::isCrosshairShown() { return showCrosshair; } bool FITSView::isEQGridShown() { return showEQGrid; } bool FITSView::areObjectsShown() { return showObjects; } bool FITSView::isPixelGridShown() { return showPixelGrid; } void FITSView::toggleCrosshair() { showCrosshair = !showCrosshair; updateFrame(); } void FITSView::toggleEQGrid() { showEQGrid = !showEQGrid; if (!imageData->isWCSLoaded() && !wcsWatcher.isRunning()) { QFuture future = QtConcurrent::run(imageData, &FITSData::loadWCS); wcsWatcher.setFuture(future); return; } if (image_frame != nullptr) updateFrame(); } void FITSView::toggleObjects() { showObjects = !showObjects; if (!imageData->isWCSLoaded() && !wcsWatcher.isRunning()) { QFuture future = QtConcurrent::run(imageData, &FITSData::loadWCS); wcsWatcher.setFuture(future); return; } if (image_frame != nullptr) updateFrame(); } void FITSView::toggleStars() { toggleStars(!markStars); if (image_frame != nullptr) updateFrame(); } void FITSView::toggleStarProfile() { #ifdef HAVE_DATAVISUALIZATION showStarProfile = !showStarProfile; if(showStarProfile && trackingBoxEnabled) viewStarProfile(); if(toggleProfileAction) toggleProfileAction->setChecked(showStarProfile); if(mode == FITS_NORMAL || mode == FITS_ALIGN) { if(showStarProfile) { setCursorMode(selectCursor); connect(this, SIGNAL(trackingStarSelected(int,int)), this, SLOT(move3DTrackingBox(int,int))); if(floatingToolBar && starProfileWidget) connect(starProfileWidget, SIGNAL(rejected()) , this, SLOT(toggleStarProfile())); if(starProfileWidget) connect(starProfileWidget, SIGNAL(sampleSizeUpdated(int)) , this, SLOT(resizeTrackingBox(int))); trackingBox = QRect(0, 0, 128, 128); - trackingBoxEnabled = true; - updateFrame(); + setTrackingBoxEnabled(true); } else { if(getCursorMode() == selectCursor) setCursorMode(dragCursor); disconnect(this, SIGNAL(trackingStarSelected(int,int)), this, SLOT(move3DTrackingBox(int,int))); disconnect(starProfileWidget, SIGNAL(sampleSizeUpdated(int)) , this, SLOT(resizeTrackingBox(int))); if(floatingToolBar) disconnect(starProfileWidget, SIGNAL(rejected()) , this, SLOT(toggleStarProfile())); setTrackingBoxEnabled(false); + starProfileWidget->deleteLater(); } + updateFrame(); } #endif } void FITSView::move3DTrackingBox(int x, int y) { int boxSize = trackingBox.width(); QRect starRect = QRect(x - boxSize / 2 , y - boxSize / 2, boxSize, boxSize); setTrackingBox(starRect); } void FITSView::viewStarProfile() { #ifdef HAVE_DATAVISUALIZATION if(!trackingBoxEnabled) { setTrackingBoxEnabled(true); setTrackingBox(QRect(0, 0, 128, 128)); } if(!starProfileWidget) { - starProfileWidget = new StarProfileViewer(this); + //I had to change it to nullptr instead of "this" + //because while it worked before, there was some change in QT + //With their change, if the user hid the viewer, it would come up empty next time! + //Changing it to nullptr fixes the problem + //starProfileWidget = new StarProfileViewer(this); + starProfileWidget = new StarProfileViewer(nullptr); //This is a band-aid to fix a QT bug with createWindowContainer //It will set the cursor of the Window containing the view that called the Star Profile method to the Arrow Cursor //Note that Ekos Manager is a QDialog and FitsViewer is a KXmlGuiWindow QWidget *superParent = this->parentWidget(); while(superParent->parentWidget()!=0 && !superParent->inherits("QDialog") && !superParent->inherits("KXmlGuiWindow")) superParent=superParent->parentWidget(); superParent->setCursor(Qt::ArrowCursor); //This is the end of the band-aid if(floatingToolBar) connect(starProfileWidget, SIGNAL(rejected()) , this, SLOT(toggleStarProfile())); if(mode == FITS_ALIGN || mode == FITS_NORMAL) { starProfileWidget->enableTrackingBox(true); imageData->setStarAlgorithm(ALGORITHM_CENTROID); connect(starProfileWidget, SIGNAL(sampleSizeUpdated(int)) , this, SLOT(resizeTrackingBox(int))); } } QList starCenters = imageData->getStarCentersInSubFrame(trackingBox); if(starCenters.size() == 0) { // FIXME, the following does not work anymore. //imageData->findStars(&trackingBox, true); // FIXME replacing it with this imageData->findStars(ALGORITHM_CENTROID, trackingBox); starCenters = imageData->getStarCentersInSubFrame(trackingBox); } starProfileWidget->loadData(imageData, trackingBox, starCenters); starProfileWidget->show(); starProfileWidget->raise(); if(markStars) updateFrame(); //this is to update for the marked stars #endif } void FITSView::togglePixelGrid() { showPixelGrid = !showPixelGrid; updateFrame(); } int FITSView::findStars(StarAlgorithm algorithm, const QRect &searchBox) { int count = 0; if(trackingBoxEnabled) count = imageData->findStars(algorithm, trackingBox); else count = imageData->findStars(algorithm, searchBox); return count; } void FITSView::toggleStars(bool enable) { markStars = enable; if (markStars && !imageData->areStarsSearched()) { QApplication::setOverrideCursor(Qt::WaitCursor); emit newStatus(i18n("Finding stars..."), FITS_MESSAGE); qApp->processEvents(); int count = findStars(); if (count >= 0 && isVisible()) emit newStatus(i18np("1 star detected.", "%1 stars detected.", count), FITS_MESSAGE); QApplication::restoreOverrideCursor(); } } void FITSView::processPointSelection(int x, int y) { //if (mode != FITS_GUIDE) //return; //image_data->getCenterSelection(&x, &y); //setGuideSquare(x,y); emit trackingStarSelected(x, y); } void FITSView::processMarkerSelection(int x, int y) { markerCrosshair.setX(x); markerCrosshair.setY(y); updateFrame(); } void FITSView::setTrackingBoxEnabled(bool enable) { if (enable != trackingBoxEnabled) { trackingBoxEnabled = enable; //updateFrame(); } } void FITSView::wheelEvent(QWheelEvent *event) { //This attempts to send the wheel event back to the Scroll Area if it was taken from a trackpad //It should still do the zoom if it is a mouse wheel if (event->source() == Qt::MouseEventSynthesizedBySystem) { QScrollArea::wheelEvent(event); } else { QPoint mouseCenter = getImagePoint(event->pos()); if (event->angleDelta().y() > 0) ZoomIn(); else ZoomOut(); event->accept(); cleanUpZoom(mouseCenter); } } /** This method is intended to keep key locations in an image centered on the screen while zooming. If there is a marker or tracking box, it centers on those. If not, it uses the point called viewCenter that was passed as a parameter. */ void FITSView::cleanUpZoom(QPoint viewCenter) { int x0 = 0; int y0 = 0; double scale = (currentZoom / ZOOM_DEFAULT); if (!markerCrosshair.isNull()) { x0 = markerCrosshair.x() * scale; y0 = markerCrosshair.y() * scale; } else if (trackingBoxEnabled) { x0 = trackingBox.center().x() * scale; y0 = trackingBox.center().y() * scale; } else { x0 = viewCenter.x() * scale; y0 = viewCenter.y() * scale; } ensureVisible(x0, y0, width() / 2, height() / 2); updateMouseCursor(); } /** This method converts a point from the ViewPort Coordinate System to the Image Coordinate System. */ QPoint FITSView::getImagePoint(QPoint viewPortPoint) { QWidget *w = widget(); if (w == nullptr) return QPoint(0, 0); double scale = (currentZoom / ZOOM_DEFAULT); QPoint widgetPoint = w->mapFromParent(viewPortPoint); QPoint imagePoint = QPoint(widgetPoint.x() / scale, widgetPoint.y() / scale); return imagePoint; } void FITSView::initDisplayImage() { if (imageData->channels() == 1) { rawImage = QImage(image_width, image_height, QImage::Format_Indexed8); rawImage.setColorCount(256); for (int i = 0; i < 256; i++) rawImage.setColor(i, qRgb(i, i, i)); } else { rawImage = QImage(image_width, image_height, QImage::Format_RGB32); } } /** The Following two methods allow gestures to work with trackpads. Specifically, we are targeting the pinch events, so that if one is generated, Then the pinchTriggered method will be called. If the event is not a pinch gesture, then the event is passed back to the other event handlers. */ bool FITSView::event(QEvent *event) { if (event->type() == QEvent::Gesture) return gestureEvent(dynamic_cast(event)); return QScrollArea::event(event); } bool FITSView::gestureEvent(QGestureEvent *event) { if (QGesture *pinch = event->gesture(Qt::PinchGesture)) pinchTriggered(dynamic_cast(pinch)); return true; } /** This Method works with Trackpads to use the pinch gesture to scroll in and out It stores a point to keep track of the location where the gesture started so that while you are zooming, it tries to keep that initial point centered in the view. **/ void FITSView::pinchTriggered(QPinchGesture *gesture) { if (!zooming) { zoomLocation = getImagePoint(mapFromGlobal(QCursor::pos())); zooming = true; } if (gesture->state() == Qt::GestureFinished) { zooming = false; } zoomTime++; //zoomTime is meant to slow down the zooming with a pinch gesture. if (zoomTime > 10000) //This ensures zoomtime never gets too big. zoomTime = 0; if (zooming && (zoomTime % 10 == 0)) //zoomTime is set to slow it by a factor of 10. { if (gesture->totalScaleFactor() > 1) ZoomIn(); else ZoomOut(); } cleanUpZoom(zoomLocation); } /*void FITSView::handleWCSCompletion() { //bool hasWCS = wcsWatcher.result(); if(imageData->hasWCS()) this->updateFrame(); emit wcsToggled(imageData->hasWCS()); }*/ void FITSView::syncWCSState() { bool hasWCS = imageData->hasWCS(); bool wcsLoaded = imageData->isWCSLoaded(); if (hasWCS && wcsLoaded) this->updateFrame(); emit wcsToggled(hasWCS); if (toggleEQGridAction != nullptr) toggleEQGridAction->setEnabled(hasWCS); if (toggleObjectsAction != nullptr) toggleObjectsAction->setEnabled(hasWCS); if (centerTelescopeAction != nullptr) centerTelescopeAction->setEnabled(hasWCS); } void FITSView::createFloatingToolBar() { if (floatingToolBar != nullptr) return; floatingToolBar = new QToolBar(this); auto *eff = new QGraphicsOpacityEffect(this); floatingToolBar->setGraphicsEffect(eff); eff->setOpacity(0.2); floatingToolBar->setVisible(false); floatingToolBar->setStyleSheet( "QToolBar{background: rgba(150, 150, 150, 210); border:none; color: yellow}" "QToolButton{background: transparent; border:none; color: yellow}" "QToolButton:hover{background: rgba(200, 200, 200, 255);border:solid; color: yellow}" "QToolButton:checked{background: rgba(110, 110, 110, 255);border:solid; color: yellow}"); floatingToolBar->setFloatable(true); floatingToolBar->setIconSize(QSize(25, 25)); //floatingToolBar->setMovable(true); QAction *action = nullptr; floatingToolBar->addAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), this, SLOT(ZoomIn())); floatingToolBar->addAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), this, SLOT(ZoomOut())); floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-best"), i18n("Default Zoom"), this, SLOT(ZoomDefault())); floatingToolBar->addAction(QIcon::fromTheme("zoom-fit-width"), i18n("Zoom to Fit"), this, SLOT(ZoomToFit())); floatingToolBar->addSeparator(); action = floatingToolBar->addAction(QIcon::fromTheme("crosshairs"), i18n("Show Cross Hairs"), this, SLOT(toggleCrosshair())); action->setCheckable(true); action = floatingToolBar->addAction(QIcon::fromTheme("map-flat"), i18n("Show Pixel Gridlines"), this, SLOT(togglePixelGrid())); action->setCheckable(true); toggleStarsAction = floatingToolBar->addAction(QIcon::fromTheme("kstars_stars"), i18n("Detect Stars in Image"), this, SLOT(toggleStars())); toggleStarsAction->setCheckable(true); #ifdef HAVE_DATAVISUALIZATION toggleProfileAction = floatingToolBar->addAction(QIcon::fromTheme("star-profile", QIcon(":/icons/star_profile.svg")), i18n("View Star Profile"), this, SLOT(toggleStarProfile())); toggleProfileAction->setCheckable(true); #endif if (mode == FITS_NORMAL || mode == FITS_ALIGN) { floatingToolBar->addSeparator(); toggleEQGridAction = floatingToolBar->addAction(QIcon::fromTheme("kstars_grid"), i18n("Show Equatorial Gridlines"), this, SLOT(toggleEQGrid())); toggleEQGridAction->setCheckable(true); toggleEQGridAction->setEnabled(false); toggleObjectsAction = floatingToolBar->addAction(QIcon::fromTheme("help-hint"), i18n("Show Objects in Image"), this, SLOT(toggleObjects())); toggleObjectsAction->setCheckable(true); toggleEQGridAction->setEnabled(false); centerTelescopeAction = floatingToolBar->addAction(QIcon::fromTheme("center_telescope", QIcon(":/icons/center_telescope.svg")), i18n("Center Telescope"), this, SLOT(centerTelescope())); centerTelescopeAction->setCheckable(true); centerTelescopeAction->setEnabled(false); } } /** This methood either enables or disables the scope mouse mode so you can slew your scope to coordinates just by clicking the mouse on a spot in the image. */ void FITSView::centerTelescope() { if (imageHasWCS()) { if (getCursorMode() == FITSView::scopeCursor) { setCursorMode(lastMouseMode); } else { lastMouseMode = getCursorMode(); setCursorMode(FITSView::scopeCursor); } updateFrame(); } updateScopeButton(); } void FITSView::updateScopeButton() { if (centerTelescopeAction != nullptr) { if (getCursorMode() == FITSView::scopeCursor) { centerTelescopeAction->setChecked(true); } else { centerTelescopeAction->setChecked(false); } } } /** This method just verifies if INDI is online, a telescope present, and is connected */ bool FITSView::isTelescopeActive() { #ifdef HAVE_INDI if (INDIListener::Instance()->size() == 0) { return false; } foreach (ISD::GDInterface *gd, INDIListener::Instance()->getDevices()) { INDI::BaseDevice *bd = gd->getBaseDevice(); if (gd->getType() != KSTARS_TELESCOPE) continue; if (bd == nullptr) continue; return bd->isConnected(); } return false; #else return false; #endif } void FITSView::setStarsEnabled(bool enable) { markStars = enable; if (floatingToolBar != nullptr) { foreach (QAction *action, floatingToolBar->actions()) { if (action->text() == i18n("Detect Stars in Image")) { action->setChecked(markStars); break; } } } } diff --git a/kstars/fitsviewer/fitsview.h b/kstars/fitsviewer/fitsview.h index 65b52d49c..80366eb22 100644 --- a/kstars/fitsviewer/fitsview.h +++ b/kstars/fitsviewer/fitsview.h @@ -1,292 +1,293 @@ /* FITS Label Copyright (C) 2003-2017 Jasem Mutlaq Copyright (C) 2016-2017 Robert Lancaster This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "fitscommon.h" #include #ifdef HAVE_DATAVISUALIZATION #include "starprofileviewer.h" #endif #include #include #include #include +#include #ifdef WIN32 // avoid compiler warning when windows.h is included after fitsio.h #include #endif #include #include #define MINIMUM_PIXEL_RANGE 5 #define MINIMUM_STDVAR 5 class QAction; class QEvent; class QGestureEvent; class QImage; class QLabel; class QPinchGesture; class QResizeEvent; class QToolBar; class FITSData; class FITSLabel; class FITSView : public QScrollArea { Q_OBJECT public: explicit FITSView(QWidget *parent = nullptr, FITSMode fitsMode = FITS_NORMAL, FITSScale filterType = FITS_NONE); ~FITSView(); typedef enum {dragCursor, selectCursor, scopeCursor, crosshairCursor } CursorMode; /** * @brief loadFITS Loads FITS data and display it in FITSView frame * @param inFilename FITS File name * @param silent if set, error popups are suppressed. * @note If image is successfully, loaded() signal is emitted, otherwise failed() signal is emitted. * Obtain error by calling lastError() */ void loadFITS(const QString &inFilename, bool silent = true); // Save FITS int saveFITS(const QString &newFilename); // Rescale image lineary from image_buffer, fit to window if desired bool rescale(FITSZoom type); // Access functions FITSData *getImageData() const { return imageData; } double getCurrentZoom() const { return currentZoom; } QImage getDisplayImage() const { return rawImage; } const QPixmap &getDisplayPixmap() const { return displayPixmap; } // Tracking square void setTrackingBoxEnabled(bool enable); bool isTrackingBoxEnabled() const { return trackingBoxEnabled; } QPixmap &getTrackingBoxPixmap(uint8_t margin=0); void setTrackingBox(const QRect &rect); const QRect &getTrackingBox() const { return trackingBox; } // last error const QString &lastError() const { return m_LastError; } // Overlay virtual void drawOverlay(QPainter *); // Overlay objects void drawStarCentroid(QPainter *); void drawTrackingBox(QPainter *); void drawMarker(QPainter *); void drawCrosshair(QPainter *); void drawEQGrid(QPainter *); void drawObjectNames(QPainter *painter); void drawPixelGrid(QPainter *painter); bool isCrosshairShown(); bool areObjectsShown(); bool isEQGridShown(); bool isPixelGridShown(); bool imageHasWCS(); void updateFrame(); bool isTelescopeActive(); void enterEvent(QEvent *event); void leaveEvent(QEvent *event); CursorMode getCursorMode(); void setCursorMode(CursorMode mode); void updateMouseCursor(); void updateScopeButton(); void setScopeButton(QAction *action) { centerTelescopeAction = action; } // Zoom related void cleanUpZoom(QPoint viewCenter); QPoint getImagePoint(QPoint viewPortPoint); uint16_t zoomedWidth() { return currentWidth; } uint16_t zoomedHeight() { return currentHeight; } // Star Detection int findStars(StarAlgorithm algorithm = ALGORITHM_CENTROID, const QRect &searchBox = QRect()); void toggleStars(bool enable); void setStarsEnabled(bool enable); // FITS Mode void updateMode(FITSMode fmode); FITSMode getMode() { return mode; } void setFilter(FITSScale newFilter) { filter = newFilter; } void setFirstLoad(bool value); void pushFilter(FITSScale value) { filterStack.push(value); } FITSScale popFilter() { return filterStack.pop(); } // Floating toolbar void createFloatingToolBar(); //void setLoadWCSEnabled(bool value); public slots: void wheelEvent(QWheelEvent *event); void resizeEvent(QResizeEvent *event); void ZoomIn(); void ZoomOut(); void ZoomDefault(); void ZoomToFit(); // Grids void toggleEQGrid(); void toggleObjects(); void togglePixelGrid(); void toggleCrosshair(); // Stars void toggleStars(); void toggleStarProfile(); void viewStarProfile(); void centerTelescope(); void processPointSelection(int x, int y); void processMarkerSelection(int x, int y); void move3DTrackingBox(int x, int y); void resizeTrackingBox(int newSize); protected slots: /** * @brief syncWCSState Update toolbar and actions depending on whether WCS is available or not */ void syncWCSState(); private: bool event(QEvent *event); bool gestureEvent(QGestureEvent *event); void pinchTriggered(QPinchGesture *gesture); template bool rescale(FITSZoom type); double average(); double stddev(); void calculateMaxPixel(double min, double max); void initDisplayImage(); QPointF getPointForGridLabel(); bool pointIsInImage(QPointF pt, bool scaled); void loadInFrame(); public: CursorMode lastMouseMode { selectCursor }; bool isStarProfileShown() { return showStarProfile; } protected: /// WCS Future Watcher QFutureWatcher wcsWatcher; /// FITS Future Watcher QFutureWatcher fitsWatcher; /// Cross hair QPointF markerCrosshair; /// Pointer to FITSData object FITSData *imageData { nullptr }; /// Current zoom level double currentZoom { 0 }; private: QLabel *noImageLabel { nullptr }; QPixmap noImage; QVector eqGridPoints; std::unique_ptr image_frame; uint32_t image_width { 0 }; uint32_t image_height { 0 }; /// Current width due to zoom uint16_t currentWidth { 0 }; uint16_t lastWidth { 0 }; /// Current height due to zoom uint16_t currentHeight { 0 }; uint16_t lastHeight { 0 }; /// Image zoom factor const double zoomFactor; // Original full-size image QImage rawImage; // Scaled images QImage scaledImage; // Actual pixmap after all the overlays QPixmap displayPixmap; bool firstLoad { true }; bool markStars { false }; bool showStarProfile { false }; bool showCrosshair { false }; bool showObjects { false }; bool showEQGrid { false }; bool showPixelGrid { false }; CursorMode cursorMode { selectCursor }; bool zooming { false }; int zoomTime { 0 }; QPoint zoomLocation; QString filename; FITSMode mode; FITSScale filter; QString m_LastError; QStack filterStack; // Tracking box bool trackingBoxEnabled { false }; QRect trackingBox; QPixmap trackingBoxPixmap; // Scope pixmap QPixmap redScopePixmap; // Magenta Scope Pixmap QPixmap magentaScopePixmap; // Floating toolbar QToolBar *floatingToolBar { nullptr }; QAction *centerTelescopeAction { nullptr }; QAction *toggleEQGridAction { nullptr }; QAction *toggleObjectsAction { nullptr }; QAction *toggleStarsAction { nullptr }; QAction *toggleProfileAction { nullptr }; //Star Profile Viewer #ifdef HAVE_DATAVISUALIZATION - StarProfileViewer *starProfileWidget = nullptr; + QPointer starProfileWidget; #endif signals: void newStatus(const QString &msg, FITSBar id); void debayerToggled(bool); void wcsToggled(bool); void actionUpdated(const QString &name, bool enable); void trackingStarSelected(int x, int y); void loaded(); void failed(); friend class FITSLabel; }; diff --git a/kstars/fitsviewer/starprofileviewer.cpp b/kstars/fitsviewer/starprofileviewer.cpp index 04062831a..34adbde33 100644 --- a/kstars/fitsviewer/starprofileviewer.cpp +++ b/kstars/fitsviewer/starprofileviewer.cpp @@ -1,1014 +1,1014 @@ /* StarProfileViewer Copyright (C) 2017 Robert Lancaster Based on the QT Surface Example http://doc.qt.io/qt-5.9/qtdatavisualization-surface-example.html and the QT Bars Example https://doc-snapshots.qt.io/qt5-5.9/qtdatavisualization-bars-example.html This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "starprofileviewer.h" #include using namespace QtDataVisualization; StarProfileViewer::StarProfileViewer(QWidget *parent) : QDialog(parent) { #ifdef Q_OS_OSX setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); #endif m_graph = new Q3DBars(); m_pixelValueAxis = m_graph->valueAxis(); m_xPixelAxis = m_graph->columnAxis(); m_yPixelAxis = m_graph->rowAxis(); m_pixelValueAxis->setTitle(i18n("Pixel Values")); m_pixelValueAxis->setLabelAutoRotation(30.0f); m_pixelValueAxis->setTitleVisible(true); m_xPixelAxis->setTitle(i18n("Horizontal")); m_xPixelAxis->setLabelAutoRotation(30.0f); m_xPixelAxis->setTitleVisible(true); m_yPixelAxis->setTitle(i18n("Vertical")); m_yPixelAxis->setLabelAutoRotation(30.0f); m_yPixelAxis->setTitleVisible(true); m_3DPixelSeries = new QBar3DSeries; m_3DPixelSeries->setMesh(QAbstract3DSeries::MeshBevelBar); m_graph->addSeries(m_3DPixelSeries); m_graph->activeTheme()->setLabelBackgroundEnabled(false); QWidget *container = QWidget::createWindowContainer(m_graph); if (!m_graph->hasContext()) { QMessageBox msgBox; msgBox.setText(i18n("Couldn't initialize the OpenGL context.")); msgBox.exec(); return; } QSize screenSize = m_graph->screen()->size(); container->setMinimumSize(QSize(300, 500)); container->setMaximumSize(screenSize); container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); container->setFocusPolicy(Qt::StrongFocus); this->setWindowTitle(i18n("View Star Profile")); QVBoxLayout *mainLayout = new QVBoxLayout(this); QHBoxLayout *topLayout = new QHBoxLayout(); QHBoxLayout *controlsLayout = new QHBoxLayout(); QWidget* rightWidget = new QWidget(); rightWidget->setVisible(false); QVBoxLayout *rightLayout = new QVBoxLayout(rightWidget); QGridLayout *sliderLayout = new QGridLayout(); topLayout->addWidget(container, 1); topLayout->addWidget(rightWidget); mainLayout->addLayout(topLayout); mainLayout->addLayout(controlsLayout); controlsLayout->setAlignment(Qt::AlignLeft); maxValue=new QLabel(this); maxValue->setToolTip(i18n("Maximum Value on the graph")); cutoffValue=new QLabel(this); cutoffValue->setToolTip(i18n("Cuttoff Maximum for eliminating hot pixels and bright stars.")); QCheckBox *toggleEnableCutoff= new QCheckBox(this); toggleEnableCutoff->setToolTip(i18n("Enable or Disable the Max Value Cutoff")); toggleEnableCutoff->setText(i18n("Toggle Cutoff")); toggleEnableCutoff->setChecked(false); blackPointSlider=new QSlider( Qt::Vertical, this); blackPointSlider->setToolTip(i18n("Sets the Minimum Value on the graph")); sliderLayout->addWidget(blackPointSlider,0,0); sliderLayout->addWidget(new QLabel(i18n("Min")),1,0); whitePointSlider=new QSlider( Qt::Vertical, this); whitePointSlider->setToolTip(i18n("Sets the Maximum Value on the graph")); sliderLayout->addWidget(whitePointSlider,0,1); sliderLayout->addWidget(new QLabel(i18n("Max")),1,1); cutoffSlider=new QSlider( Qt::Vertical, this); cutoffSlider->setToolTip(i18n("Sets the Cuttoff Maximum for eliminating hot pixels and bright stars.")); sliderLayout->addWidget(cutoffSlider,0,2); sliderLayout->addWidget(new QLabel(i18n("Cut")),1,2); cutoffSlider->setEnabled(false); minValue = new QLabel(this); minValue->setToolTip(i18n("Minimum Value on the graph")); autoScale = new QCheckBox(this); autoScale->setText(i18n("AutoScale")); autoScale->setToolTip(i18n("Automatically scales the sliders for the subFrame.\nUncheck to leave them unchanged when you pan around.")); autoScale->setChecked(true); showScaling = new QPushButton(this); showScaling->setIcon(QIcon::fromTheme("transform-move-vertical")); showScaling->setCheckable(true); showScaling->setMaximumSize(22, 22); showScaling->setAttribute(Qt::WA_LayoutUsesWidgetRect); showScaling->setToolTip(i18n("Hides and shows the scaling side panel")); showScaling->setChecked(false); rightLayout->addWidget(toggleEnableCutoff); rightLayout->addWidget(cutoffValue); rightLayout->addWidget(maxValue); rightLayout->addLayout(sliderLayout); rightLayout->addWidget(minValue); rightLayout->addWidget(autoScale); selectionType = new QComboBox(this); selectionType->setToolTip(i18n("Changes the type of selection")); selectionType->addItem(i18n("Item")); selectionType->addItem(i18n("Horizontal")); selectionType->addItem(i18n("Vertical")); selectionType->setCurrentIndex(0); sliceB = new QPushButton(this); sliceB->setIcon(QIcon::fromTheme("view-object-histogram-linear")); sliceB->setCheckable(true); sliceB->setMaximumSize(22, 22); sliceB->setAttribute(Qt::WA_LayoutUsesWidgetRect); sliceB->setToolTip(i18n("Toggles the slice view when horizontal or vertical items are selected")); sliceB->setCheckable(true); sliceB->setChecked(false); sliceB->setEnabled(false); sliceB->setDefault(false); showCoordinates = new QPushButton(this); showCoordinates->setIcon(QIcon::fromTheme("coordinate")); showCoordinates->setCheckable(true); showCoordinates->setMaximumSize(22, 22); showCoordinates->setAttribute(Qt::WA_LayoutUsesWidgetRect); showCoordinates->setToolTip(i18n("Shows the x, y coordinates of star centers in the frame")); showCoordinates->setChecked(false); HFRReport = new QPushButton(this); HFRReport->setToolTip(i18n("Shows the HFR of stars in the frame")); HFRReport->setIcon(QIcon::fromTheme("tool-measure")); HFRReport->setCheckable(true); HFRReport->setMaximumSize(22, 22); HFRReport->setAttribute(Qt::WA_LayoutUsesWidgetRect); HFRReport->setChecked(true); reportBox = new QLabel(this); showPeakValues = new QPushButton(this); showPeakValues->setIcon(QIcon::fromTheme("kruler-east")); showPeakValues->setCheckable(true); showPeakValues->setMaximumSize(22, 22); showPeakValues->setAttribute(Qt::WA_LayoutUsesWidgetRect); showPeakValues->setToolTip(i18n("Shows the peak values of star centers in the frame")); showPeakValues->setChecked(true); sampleSize = new QComboBox(this); sampleSize->setToolTip(i18n("Changes the sample size shown in the graph")); sampleSize->addItem(QString::number(16)); sampleSize->addItem(QString::number(32)); sampleSize->addItem(QString::number(64)); sampleSize->addItem(QString::number(128)); sampleSize->addItem(QString::number(256)); sampleSize->addItem(QString::number(512)); sampleSize->setCurrentIndex(3); sampleSize->setVisible(false); zoomView = new QComboBox(this); zoomView->setToolTip(i18n("Zooms the view to preset locations.")); zoomView->addItem(i18n("ZoomTo:")); zoomView->addItem(i18n("Front")); zoomView->addItem(i18n("Front High")); zoomView->addItem(i18n("Overhead")); zoomView->addItem(i18n("Iso. L")); zoomView->addItem(i18n("Iso. R")); zoomView->addItem(i18n("Selected")); zoomView->setCurrentIndex(0); QPushButton *selectorsVisible = new QPushButton(this); selectorsVisible->setIcon(QIcon::fromTheme("adjustlevels")); selectorsVisible->setCheckable(true); selectorsVisible->setMaximumSize(22, 22); selectorsVisible->setAttribute(Qt::WA_LayoutUsesWidgetRect); selectorsVisible->setToolTip(i18n("Hides and shows the Vertical and Horizontal Selection Sliders")); selectorsVisible->setChecked(false); controlsLayout->addWidget(sampleSize); controlsLayout->addWidget(selectionType); controlsLayout->addWidget(selectorsVisible); controlsLayout->addWidget(sliceB); controlsLayout->addWidget(showScaling); //bottomLayout->addWidget(barSpacing); controlsLayout->addWidget(zoomView); //bottomLayout->addWidget(color); controlsLayout->addWidget(showCoordinates); controlsLayout->addWidget(HFRReport); controlsLayout->addWidget(showPeakValues); controlsLayout->addWidget(reportBox); QWidget *bottomSliderWidget= new QWidget(this); QGridLayout *bottomSliders = new QGridLayout(bottomSliderWidget); bottomSliderWidget->setLayout(bottomSliders); mainLayout->addWidget(bottomSliderWidget); bottomSliderWidget->setVisible(false); verticalSelector = new QSlider(Qt::Horizontal, this); verticalSelector->setToolTip(i18n("Selects the Vertical Value")); horizontalSelector = new QSlider(Qt::Horizontal, this); horizontalSelector->setToolTip(i18n("Selects the Horizontal Value")); bottomSliders->addWidget(new QLabel(i18n("Vertical: ")), 0, 0); bottomSliders->addWidget(verticalSelector, 0, 1); bottomSliders->addWidget(new QLabel(i18n("Horizontal: ")), 1, 0); bottomSliders->addWidget(horizontalSelector, 1, 1); QWidget *bottomControlsWidget= new QWidget(this); QHBoxLayout *bottomControlLayout = new QHBoxLayout(bottomControlsWidget); mainLayout->addWidget(bottomControlsWidget);\ bottomControlsWidget->setVisible(false); exploreMode = new QPushButton(this); exploreMode->setIcon(QIcon::fromTheme("visibility")); exploreMode->setCheckable(true); exploreMode->setMaximumSize(22, 22); exploreMode->setAttribute(Qt::WA_LayoutUsesWidgetRect); exploreMode->setToolTip(i18n("Zooms automatically as the sliders change")); exploreMode->setChecked(true); QDial *barSpacing=new QDial(this); barSpacing->setMinimum(0); barSpacing->setMaximum(100); barSpacing->setValue(50); barSpacing->setMaximumSize(32, 32); barSpacing->setWrapping(false); m_graph->setBarSpacing(QSizeF(0.5,0.5)); QComboBox *color = new QComboBox(this); color->setToolTip(i18n("Changes the color scheme")); QLinearGradient grGtoR(50, 1, 0, 0); grGtoR.setColorAt(1.0, Qt::darkGreen); grGtoR.setColorAt(0.5, Qt::yellow); grGtoR.setColorAt(0.2, Qt::red); grGtoR.setColorAt(0.0, Qt::darkRed); QPixmap pm(50, 10); QPainter pmp(&pm); pmp.setPen(Qt::NoPen); pmp.setBrush(QBrush(grGtoR)); pmp.drawRect(0, 0, 50, 10); color->addItem(""); color->setItemIcon(0,QIcon(pm)); QLinearGradient grBtoY(50, 1, 0, 0); grBtoY.setColorAt(1.0, Qt::black); grBtoY.setColorAt(0.67, Qt::blue); grBtoY.setColorAt(0.33, Qt::red); grBtoY.setColorAt(0.0, Qt::yellow); pmp.setBrush(QBrush(grBtoY)); pmp.drawRect(0, 0, 50, 10); color->addItem(""); color->setItemIcon(1,QIcon(pm)); color->setIconSize(QSize(50, 10)); color->setCurrentIndex(0); color->setMaximumWidth(80); pixelReport = new QLabel("", bottomControlsWidget); bottomControlLayout->addWidget(exploreMode); bottomControlLayout->addWidget(barSpacing); bottomControlLayout->addWidget(color); bottomControlLayout->addWidget(pixelReport); QObject::connect(selectionType, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSelectionType(int))); QObject::connect(zoomView, SIGNAL(currentIndexChanged(int)), this, SLOT(zoomViewTo(int))); QObject::connect(sliceB, &QPushButton::pressed, this, &StarProfileViewer::toggleSlice); QObject::connect(showCoordinates, &QCheckBox::toggled, this, &StarProfileViewer::updateHFRandPeakSelection); QObject::connect(HFRReport, &QCheckBox::toggled, this, &StarProfileViewer::updateHFRandPeakSelection); QObject::connect(showPeakValues, &QCheckBox::toggled, this, &StarProfileViewer::updateHFRandPeakSelection); QObject::connect(blackPointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::connect(whitePointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::connect(cutoffSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateDisplayData); QObject::connect(autoScale, &QCheckBox::toggled, this, &StarProfileViewer::updateScale); QObject::connect(showScaling, &QCheckBox::toggled, rightWidget, &QWidget::setVisible); QObject::connect(sampleSize, SIGNAL(currentIndexChanged(QString)), this, SLOT(updateSampleSize(QString))); QObject::connect(color, SIGNAL(currentIndexChanged(int)), this, SLOT(updateColor(int))); QObject::connect(verticalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); QObject::connect(horizontalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); QObject::connect(selectorsVisible, &QCheckBox::toggled, bottomSliderWidget, &QWidget::setVisible); QObject::connect(selectorsVisible, &QCheckBox::toggled, bottomControlsWidget, &QWidget::setVisible); QObject::connect(toggleEnableCutoff, &QCheckBox::toggled, this, &StarProfileViewer::toggleCutoffEnabled); QObject::connect(m_3DPixelSeries, &QBar3DSeries::selectedBarChanged, this, &StarProfileViewer::updateSelectorBars); QObject::connect(barSpacing, &QSlider::valueChanged, this, &StarProfileViewer::updateBarSpacing); m_graph->activeTheme()->setType(Q3DTheme::Theme(3)); //Stone Moss setGreenToRedGradient(); m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); //Note: This is to prevent a button from being called the default button //and then executing when the user hits the enter key such as when on a Text Box #ifdef Q_OS_OSX QList qButtons = findChildren(); for (auto &button : qButtons) button->setAutoDefault(false); #endif show(); } StarProfileViewer::~StarProfileViewer() { delete m_graph; } void StarProfileViewer::loadData(FITSData * data, QRect sub, QList centers) { if(data) { imageData = data; subFrame=sub; starCenters=centers; switch (data->property("dataType").toInt()) { case TBYTE: loadDataPrivate(); break; case TSHORT: loadDataPrivate(); break; case TUSHORT: loadDataPrivate(); break; case TLONG: loadDataPrivate(); break; case TULONG: loadDataPrivate(); break; case TFLOAT: loadDataPrivate(); break; case TLONGLONG: loadDataPrivate(); break; case TDOUBLE: loadDataPrivate(); break; } updateScale(); // Add data to the data proxy (the data proxy assumes ownership of it) // We will retain a copy of the data set so that we can update the display updateDisplayData(); updateHFRandPeakSelection(); horizontalSelector->setRange(0, subFrame.width()-1); verticalSelector->setRange(0, subFrame.width()-1); //Width and height are the same } } template void StarProfileViewer::loadDataPrivate() { // Create data arrays dataSet = new QBarDataArray; QBarDataRow *dataRow; dataSet->reserve(subFrame.height()); QStringList rowLabels; QStringList columnLabels; auto *buffer = reinterpret_cast(imageData->getImageBuffer()); int width = imageData->width(); for (int j = subFrame.y(); j < subFrame.y() + subFrame.height(); j++) { if( j % 10 == 0 ) rowLabels << QString::number(j); else rowLabels << ""; dataRow = new QBarDataRow(subFrame.width()); int x = 0; for (int i = subFrame.x(); i < subFrame.x() + subFrame.width(); i++) { if( i % 10 == 0 ) columnLabels << QString::number(i); else columnLabels << ""; if( i > 0 && i < imageData->width() && j > 0 && j < imageData->height()) (*dataRow)[x].setValue(*(buffer + i + j * width)); x++; } dataSet->insert(0, dataRow); //Note the row axis is displayed in the opposite direction of the y axis in the image. } std::reverse(rowLabels.begin(), rowLabels.end()); m_3DPixelSeries->dataProxy()->setRowLabels(rowLabels); m_3DPixelSeries->dataProxy()->setColumnLabels(columnLabels); } void StarProfileViewer::toggleCutoffEnabled(bool enable) { cutoffSlider->setEnabled(enable); cutOffEnabled = enable; updateDisplayData(); } void StarProfileViewer::updateScale() { //We need to disconnect these so that changing their ranges doesn't affect things QObject::disconnect(blackPointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::disconnect(whitePointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::disconnect(cutoffSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateDisplayData); float subFrameMin, subFrameMax; double dataMin, dataMax; float min, max; getSubFrameMinMax(&subFrameMin, &subFrameMax, &dataMin, &dataMax); int sliderDataMin = convertToSliderValue(dataMin) - 1; //Expands the slider range a little beyond the max and min values int sliderDataMax = convertToSliderValue(dataMax) + 1; if(autoScale->isChecked()) { min = subFrameMin; max = subFrameMax; int sliderMin = convertToSliderValue(min) - 1; //Expands the slider range a little beyond the max and min values int sliderMax = convertToSliderValue(max) + 1; blackPointSlider->setRange(sliderMin, sliderMax); blackPointSlider->setTickInterval((sliderMax - sliderMin) / 100); whitePointSlider->setRange(sliderMin, sliderMax); whitePointSlider->setTickInterval((sliderMax - sliderMin) / 100); cutoffSlider->setRange(sliderMin, sliderDataMax); cutoffSlider->setTickInterval((sliderDataMax - sliderMin) / 100); blackPointSlider->setValue(sliderMin); whitePointSlider->setValue(sliderMax); cutoffSlider->setValue(sliderDataMax); } else { min = convertFromSliderValue(blackPointSlider->value()); max = convertFromSliderValue(whitePointSlider->value()); blackPointSlider->setRange(sliderDataMin, sliderDataMax); blackPointSlider->setTickInterval((sliderDataMax - sliderDataMin) / 100); whitePointSlider->setRange(sliderDataMin, sliderDataMax); whitePointSlider->setTickInterval((sliderDataMax - sliderDataMin) / 100); cutoffSlider->setRange(sliderDataMin, sliderDataMax); cutoffSlider->setTickInterval((sliderDataMax - sliderDataMin) / 100); } m_pixelValueAxis->setRange(min, max); if(cutOffEnabled) cutoffValue->setText(i18n("Cut: %1", roundf(convertFromSliderValue(cutoffSlider->value()) * 100) / 100)); else cutoffValue->setText("Cut Disabled"); if(max < 10 ) { m_pixelValueAxis->setLabelFormat(QString(QStringLiteral("%.3f "))); m_3DPixelSeries->setItemLabelFormat(QString(QStringLiteral("%.3f "))); maxValue->setText(i18n("Max: %1", roundf(max * 100) / 100)); minValue->setText(i18n("Min: %1", roundf(min * 100) / 100)); } else { m_pixelValueAxis->setLabelFormat(QString(QStringLiteral("%.0f "))); m_3DPixelSeries->setItemLabelFormat(QString(QStringLiteral("%.0f "))); maxValue->setText(i18n("Max: %1", max)); minValue->setText(i18n("Min: %1", min)); } QObject::connect(blackPointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::connect(whitePointSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateVerticalAxis); QObject::connect(cutoffSlider, &QSlider::valueChanged, this, &StarProfileViewer::updateDisplayData); } void StarProfileViewer::updateBarSpacing(int value) { float spacing = (float)value/100.0; m_graph->setBarSpacing(QSizeF(spacing, spacing)); } void StarProfileViewer::zoomViewTo(int where) { if(where > 6) //One of the star centers { int star = where - 7; int x = starCenters[star]->x - subFrame.x(); int y = subFrame.height() - (starCenters[star]->y - subFrame.y()); m_graph->primarySeries()->setSelectedBar(QPoint( y , x )); //Note row, column y, x where = 6; //This is so it will zoom to the target. } switch (where) { case 0: //Zoom To break; case 1: //Front m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFront); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 2: //Front High m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetFrontHigh); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 3: //Overhead m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetDirectlyAbove); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 4: //Isometric L m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricLeftHigh); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 5: //Isometric R m_graph->scene()->activeCamera()->setCameraPreset(Q3DCamera::CameraPresetIsometricRightHigh); m_graph->scene()->activeCamera()->setTarget(QVector3D(0.0f, 0.0f, 0.0f)); m_graph->scene()->activeCamera()->setZoomLevel(110); zoomView->setCurrentIndex(0); break; case 6: //Selected Item { QPoint selectedBar = m_graph->selectedSeries() ? m_graph->selectedSeries()->selectedBar() : QBar3DSeries::invalidSelectionPosition(); if (selectedBar != QBar3DSeries::invalidSelectionPosition()) { QVector3D target; float xMin = m_graph->columnAxis()->min(); float xRange = m_graph->columnAxis()->max() - xMin; float zMin = m_graph->rowAxis()->min(); float zRange = m_graph->rowAxis()->max() - zMin; target.setX((selectedBar.y() - xMin) / xRange * 2.0f - 1.0f); target.setZ((selectedBar.x() - zMin) / zRange * 2.0f - 1.0f); qreal endAngleX = qAtan(qreal(target.z() / target.x())) / M_PI * -180.0 + 90.0; if (target.x() > 0.0f) endAngleX -= 180.0f; float barValue = m_graph->selectedSeries()->dataProxy()->itemAt(selectedBar.x(), selectedBar.y())->value(); float endAngleY = 60.0f; float zoom = 150 * 1/qSqrt(barValue / convertFromSliderValue(whitePointSlider->value())); m_graph->scene()->activeCamera()->setCameraPosition(endAngleX, endAngleY, zoom); m_graph->scene()->activeCamera()->setTarget(target); } zoomView->setCurrentIndex(0); break; } default: zoomView->setCurrentIndex(0); break; } } void StarProfileViewer::changeSelectionType(int type) { switch (type) { case 0: m_graph->setSelectionMode(QAbstract3DGraph::SelectionItem); m_graph->scene()->setSlicingActive(false); sliceB->setEnabled(false); break; case 1: m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndRow); sliceB->setEnabled(true); break; case 2: m_graph->setSelectionMode(QAbstract3DGraph::SelectionItemAndColumn); sliceB->setEnabled(true); break; default: break; } } void StarProfileViewer::changeSelection() { int x = horizontalSelector->value(); int y = verticalSelector->value(); m_graph->primarySeries()->setSelectedBar(QPoint( y , x )); //Note row, column y, x if(exploreMode->isChecked()) zoomViewTo(6); //Zoom to SelectedItem updatePixelReport(); } void StarProfileViewer::updatePixelReport() { int x = horizontalSelector->value(); int y = verticalSelector->value(); //They need to be shifted to the location of the subframe x += subFrame.x(); y = (subFrame.height() - 1 - y) + subFrame.y(); //Note: Y is in reverse order on the graph. float barValue = getImageDataValue(x, y); pixelReport->setText(i18n("Selected Pixel: (%1, %2): %3", x + 1, y + 1, roundf(barValue * 100) / 100)); //Have to add 1 because humans start counting at 1 } void StarProfileViewer::updateSelectorBars(QPoint position) { //Note that we need to disconnect and then reconnect to avoid triggering changeSelection QObject::disconnect(verticalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); QObject::disconnect(horizontalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); //Note row, column y, x verticalSelector->setValue(position.x()); horizontalSelector->setValue(position.y()); updatePixelReport(); QObject::connect(verticalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); QObject::connect(horizontalSelector, &QSlider::valueChanged, this, &StarProfileViewer::changeSelection); } void StarProfileViewer::updateSampleSize(const QString &text) { emit sampleSizeUpdated(text.toInt()); } void StarProfileViewer::enableTrackingBox(bool enable) { sampleSize->setVisible(enable); } void StarProfileViewer::updateDisplayData() { if(cutOffEnabled) cutoffValue->setText(i18n("Cut: %1", roundf(convertFromSliderValue(cutoffSlider->value()) * 100) / 100)); else cutoffValue->setText(i18n("Cut Disabled")); if(dataSet != nullptr) { QBarDataArray *displayDataSet = new QBarDataArray; displayDataSet->reserve(dataSet->size()); for (int row = 0; row < dataSet->size(); row++) { QBarDataRow *dataRow = dataSet->at(row); QBarDataRow *newDataRow; newDataRow = new QBarDataRow(dataRow->size()); for (int column = 0; column < dataRow->size(); column++) { if(cutOffEnabled && dataRow->value(column).value() > convertFromSliderValue(cutoffSlider->value())) (*newDataRow)[column].setValue(0.0f); else (*newDataRow)[column].setValue(dataRow->value(column).value()); } displayDataSet->append(newDataRow); } m_3DPixelSeries->dataProxy()->resetArray(displayDataSet); //, m_3DPixelSeries->dataProxy()->rowLabels(), m_3DPixelSeries->dataProxy()->columnLabels() } } void StarProfileViewer::getSubFrameMinMax(float *subFrameMin, float *subFrameMax, double *dataMin, double *dataMax) { imageData->getMinMax(dataMin,dataMax); //Backwards so that we can find the min and max in subFrame *subFrameMin = *dataMax; *subFrameMax = *dataMin; switch (imageData->property("dataType").toInt()) { case TBYTE: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TSHORT: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TUSHORT: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TLONG: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TULONG: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TFLOAT: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TLONGLONG: getSubFrameMinMax(subFrameMin, subFrameMax); break; case TDOUBLE: getSubFrameMinMax(subFrameMin, subFrameMax); break; } } template void StarProfileViewer::getSubFrameMinMax(float *subFrameMin, float *subFrameMax) { T *buffer = reinterpret_cast(imageData->getImageBuffer()); T min = std::numeric_limits::max(); T max = std::numeric_limits::min(); int width = imageData->width(); for (int y = subFrame.y(); y < subFrame.y() + subFrame.height(); y++) { for (int x = subFrame.x(); x < subFrame.x() + subFrame.width(); x++) { if( x > 0 && x < imageData->width() && y > 0 && y < imageData->height()) { min = qMin(min, *(buffer + x + y * width)); - max = qMax(min, *(buffer + x + y * width)); + max = qMax(max, *(buffer + x + y * width)); } } } *subFrameMin = min; *subFrameMax = max; } template float StarProfileViewer::getImageDataValue(int x, int y) { if(!imageData) return 0; uint8_t *image_buffer = imageData->getImageBuffer(); T *buffer = reinterpret_cast(image_buffer); return (float) buffer[y * imageData->width() + x]; } float StarProfileViewer::getImageDataValue(int x, int y) { switch (imageData->property("dataType").toInt()) { case TBYTE: return getImageDataValue(x, y); break; case TSHORT: return getImageDataValue(x, y); break; case TUSHORT: return getImageDataValue(x, y); break; case TLONG: return getImageDataValue(x, y); break; case TULONG: return getImageDataValue(x, y); break; case TFLOAT: return getImageDataValue(x, y); break; case TLONGLONG: return getImageDataValue(x, y); break; case TDOUBLE: return getImageDataValue(x, y); break; default: return 0; break; } } void StarProfileViewer::toggleSlice() { if(m_graph->selectionMode() == QAbstract3DGraph::SelectionItemAndRow || m_graph->selectionMode() == QAbstract3DGraph::SelectionItemAndColumn) { if(m_graph->scene()->isSlicingActive()) { m_graph->scene()->setSlicingActive(false); } else { QPoint selectedBar = m_graph->selectedSeries() ? m_graph->selectedSeries()->selectedBar() : QBar3DSeries::invalidSelectionPosition(); if (selectedBar != QBar3DSeries::invalidSelectionPosition()) m_graph->scene()->setSlicingActive(true); } } } void StarProfileViewer::updateVerticalAxis() { float blackPoint = convertFromSliderValue(blackPointSlider->value()); float whitePoint = convertFromSliderValue(whitePointSlider->value()); m_pixelValueAxis->setRange(blackPoint, whitePoint); maxValue->setText(i18n("Max: %1", roundf(whitePoint * 100) / 100)); minValue->setText(i18n("Min: %1", roundf(blackPoint * 100) / 100)); } void StarProfileViewer::updateHFRandPeakSelection() { m_graph->removeCustomItems(); reportBox->setText(""); QString reportString = ""; //Removes all the stars from the combo box. while(zoomView->count() > 7) zoomView->removeItem(7); for (int i = 0; i < starCenters.count(); i++) { int x = starCenters[i]->x; int row = x - subFrame.x(); int y = starCenters[i]->y; int col = subFrame.height() - (y - subFrame.y()); if(subFrame.contains(x,y)){ double newHFR = imageData->getHFR(x,y); int value = getImageDataValue(x, y); QCustom3DLabel *label = new QCustom3DLabel(); label->setFacingCamera(true); QString labelString = i18n("Star %1: ", i + 1); if(showCoordinates->isChecked()) { labelString = labelString + i18n("(%1, %2) ", x + 1, y + 1); } if(HFRReport->isChecked()) { labelString = labelString + i18n("HFR: %1 ", roundf(newHFR * 100) / 100); } if(showPeakValues->isChecked()) { labelString = labelString + i18n("Peak: %1", value); } if(showCoordinates->isChecked() || HFRReport->isChecked() || showPeakValues->isChecked()) { if (!reportString.isEmpty()) reportString += '\n'; reportString += labelString; label->setText(labelString); label->setPosition(QVector3D(row, value, col)); label->setScaling(QVector3D(1.0f, 1.0f, 1.0f)); m_graph->addCustomItem(label); } //Adds this star to the combo box. zoomView->addItem(i18n("Star %1", i + 1)); } } if (!reportString.isEmpty()) { reportBox->setText(reportString); } } void StarProfileViewer::updateColor(int selection) { switch (selection) { case 0: setGreenToRedGradient(); break; case 1: setBlackToYellowGradient(); break; default: break; } } void StarProfileViewer::setBlackToYellowGradient() { QLinearGradient gr; gr.setColorAt(0.0, Qt::black); gr.setColorAt(0.33, Qt::blue); gr.setColorAt(0.67, Qt::red); gr.setColorAt(1.0, Qt::yellow); QLinearGradient highGr; highGr.setColorAt(0.0, Qt::yellow); highGr.setColorAt(1.0, Qt::yellow); QLinearGradient sinHighGr; sinHighGr.setColorAt(0.0, Qt::red); sinHighGr.setColorAt(1.0, Qt::red); m_3DPixelSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient); m_3DPixelSeries->setBaseGradient(gr); m_3DPixelSeries->setSingleHighlightGradient(sinHighGr); m_3DPixelSeries->setMultiHighlightGradient(highGr); } void StarProfileViewer::setGreenToRedGradient() { QLinearGradient gr; gr.setColorAt(0.0, Qt::darkGreen); gr.setColorAt(0.5, Qt::yellow); gr.setColorAt(0.8, Qt::red); gr.setColorAt(1.0, Qt::darkRed); QLinearGradient highGr; highGr.setColorAt(0.0, Qt::black); highGr.setColorAt(1.0, Qt::black); QLinearGradient sinHighGr; sinHighGr.setColorAt(0.0, Qt::red); sinHighGr.setColorAt(1.0, Qt::red); m_3DPixelSeries->setBaseGradient(gr); m_3DPixelSeries->setColorStyle(Q3DTheme::ColorStyleRangeGradient); m_3DPixelSeries->setSingleHighlightGradient(sinHighGr); m_3DPixelSeries->setMultiHighlightGradient(highGr); } //Multiplying by 1000000 will take care of preserving decimals in an int slider //The sqrt function makes the slider non-linear, emphasising the lower values //Note that it is actually multiplying the number on the slider by 1000 or so since it is square rooted. int StarProfileViewer::convertToSliderValue(float value) { return (int) qSqrt((value * 1000000.0)); } float StarProfileViewer::convertFromSliderValue(int value) { return qPow((float)value,2) / 1000000.0; } diff --git a/kstars/fitsviewer/starprofileviewer.h b/kstars/fitsviewer/starprofileviewer.h index 96102e85c..47946e433 100644 --- a/kstars/fitsviewer/starprofileviewer.h +++ b/kstars/fitsviewer/starprofileviewer.h @@ -1,126 +1,126 @@ /* StarProfileViewer Copyright (C) 2017 Robert Lancaster Based on the QT Surface Example http://doc.qt.io/qt-5.9/qtdatavisualization-surface-example.html and the QT Bars Example https://doc-snapshots.qt.io/qt5-5.9/qtdatavisualization-bars-example.html This application is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #pragma once #include "fitsdata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace QtDataVisualization; class StarProfileViewer : public QDialog { Q_OBJECT public: - explicit StarProfileViewer(QWidget *parent = nullptr); + explicit StarProfileViewer(QWidget *parent); ~StarProfileViewer(); void setBlackToYellowGradient(); void setGreenToRedGradient(); void loadData(FITSData *imageData, QRect sub, QList starCenters); template void loadDataPrivate(); float getImageDataValue(int x, int y); void toggleSlice(); void updateVerticalAxis(); void updateHFRandPeakSelection(); void updateDisplayData(); void updateScale(); void enableTrackingBox(bool enable); void changeSelection(); void updateSelectorBars(QPoint position); void toggleCutoffEnabled(bool enable); public slots: void changeSelectionType(int type); void zoomViewTo(int where); void updateSampleSize(const QString &text); void updateColor(int selection); void updateBarSpacing(int value); signals: void sampleSizeUpdated(int size); private: Q3DBars *m_graph { nullptr }; QValue3DAxis *m_pixelValueAxis { nullptr }; QCategory3DAxis *m_xPixelAxis { nullptr }; QCategory3DAxis *m_yPixelAxis { nullptr }; QBar3DSeries *m_3DPixelSeries { nullptr }; QBarDataArray *dataSet { nullptr }; template float getImageDataValue(int x, int y); void getSubFrameMinMax(float *subFrameMin, float *subFrameMax, double *dataMin, double *dataMax); template void getSubFrameMinMax(float *subFrameMin, float *subFrameMax); QPushButton *HFRReport { nullptr }; QLabel *reportBox { nullptr }; QPushButton *showPeakValues { nullptr }; QPushButton *showCoordinates { nullptr }; QCheckBox *autoScale { nullptr }; QPushButton *showScaling { nullptr }; QComboBox *sampleSize { nullptr }; QComboBox *selectionType { nullptr }; QComboBox *zoomView { nullptr }; QComboBox *selectStar { nullptr }; QPushButton *exploreMode { nullptr }; QLabel *pixelReport { nullptr }; QLabel *maxValue { nullptr }; QLabel *minValue { nullptr }; QLabel *cutoffValue { nullptr }; QPushButton *sliceB { nullptr }; FITSData * imageData { nullptr }; QRect subFrame; QSlider *blackPointSlider { nullptr }; QSlider *whitePointSlider { nullptr }; QSlider *cutoffSlider { nullptr }; QSlider *verticalSelector { nullptr }; QSlider *horizontalSelector { nullptr }; QList starCenters; bool cutOffEnabled { false }; int convertToSliderValue(float value); float convertFromSliderValue(int value); void updatePixelReport(); };