| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134 |
- //##########################################################################
- //# #
- //# CLOUDCOMPARE #
- //# #
- //# This program 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; version 2 or later of the License. #
- //# #
- //# This program is distributed in the hope that it will be useful, #
- //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
- //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
- //# GNU General Public License for more details. #
- //# #
- //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
- //# #
- //##########################################################################
- #include "ccHistogramWindow.h"
- #include "ccGuiParameters.h"
- //Local
- #include "ccQCustomPlot.h"
- #include "ccPersistentSettings.h"
- //qCC_db
- #include <ccColorScalesManager.h>
- #include <ccFileUtils.h>
- //qCC_io
- #include <ImageFileFilter.h>
- //Qt
- #include <QCloseEvent>
- #include <QFile>
- #include <QTextStream>
- #include <QSettings>
- #include <QFileDialog>
- //System
- #include <assert.h>
- #include <cmath>
- //Gui
- #include "ui_histogramDlg.h"
- ccHistogramWindow::ccHistogramWindow(QWidget* parent/*=nullptr*/)
- : QCustomPlot(parent)
- , m_titlePlot(nullptr)
- , m_colorScheme(USE_SOLID_COLOR)
- , m_solidColor(Qt::blue)
- , m_colorScale(ccColorScalesManager::GetDefaultScale())
- , m_associatedSF(nullptr)
- , m_numberOfClassesCanBeChanged(false)
- , m_histogram(nullptr)
- , m_minVal(0)
- , m_maxVal(0)
- , m_maxHistoVal(0)
- , m_overlayCurve(nullptr)
- , m_vertBar(nullptr)
- , m_drawVerticalIndicator(false)
- , m_verticalIndicatorPositionPercent(0)
- , m_sfInteractionModes(SFInteractionMode::None)
- , m_axisDisplayOptions(AxisDisplayOption::All)
- , m_selectedItem(NONE)
- , m_areaLeft(nullptr)
- , m_areaLeftlastValue(std::numeric_limits<double>::quiet_NaN())
- , m_areaRight(nullptr)
- , m_areaRightlastValue(std::numeric_limits<double>::quiet_NaN())
- , m_arrowLeft(nullptr)
- , m_arrowLeftlastValue(std::numeric_limits<double>::quiet_NaN())
- , m_arrowRight(nullptr)
- , m_arrowRightlastValue(std::numeric_limits<double>::quiet_NaN())
- , m_lastMouseClick(0, 0)
- , m_refreshAfterResize(true)
- {
- setWindowTitle("Histogram");
- setFocusPolicy(Qt::StrongFocus);
- setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
- setAutoAddPlottableToLegend(false);
- setAntialiasedElements(QCP::AntialiasedElement::aeAll);
- //default font for text rendering
- m_renderingFont.setFamily(QString::fromUtf8("Arial"));
- m_renderingFont.setBold(false);
- //m_renderingFont.setWeight(75);
- // make ticks on bottom axis go outward
- assert(xAxis && yAxis);
- xAxis->setTickLength(0, 5);
- xAxis->setSubTickLength(0, 3);
- yAxis->setTickLength(0, 5);
- yAxis->setSubTickLength(0, 3);
- }
- ccHistogramWindow::~ccHistogramWindow()
- {
- clearInternal();
- }
- void ccHistogramWindow::clear()
- {
- clearInternal();
- refresh();
- }
- void ccHistogramWindow::clearInternal()
- {
- if (m_associatedSF)
- {
- m_associatedSF->release();
- m_associatedSF = nullptr;
- }
- m_histoValues.resize(0);
- m_maxHistoVal = 0;
- m_curveValues.resize(0);
- m_selectedItem = NONE;
- }
- void ccHistogramWindow::setTitle(const QString& str)
- {
- m_titleStr = str;
- }
- void ccHistogramWindow::setAxisLabels(const QString& xLabel, const QString& yLabel)
- {
- if (xLabel.isNull())
- {
- xAxis->setVisible(false);
- }
- else
- {
- // set labels
- xAxis->setLabel(xLabel);
- xAxis->setVisible(true);
- }
- if (yLabel.isNull())
- {
- yAxis->setVisible(false);
- }
- else
- {
- // set labels
- yAxis->setLabel(yLabel);
- yAxis->setVisible(true);
- }
- }
- void ccHistogramWindow::fromSF( ccScalarField* sf,
- unsigned initialNumberOfClasses/*=0*/,
- bool numberOfClassesCanBeChanged/*=true*/,
- bool showNaNValuesInGrey/*=true*/)
- {
- if (sf && m_associatedSF != sf)
- {
- if (m_associatedSF)
- m_associatedSF->release();
- m_associatedSF = sf;
- if (m_associatedSF)
- m_associatedSF->link();
- }
- if (m_associatedSF)
- {
- m_minVal = showNaNValuesInGrey ? m_associatedSF->getMin() : m_associatedSF->displayRange().start();
- m_maxVal = showNaNValuesInGrey ? m_associatedSF->getMax() : m_associatedSF->displayRange().stop();
- m_numberOfClassesCanBeChanged = numberOfClassesCanBeChanged;
- }
- else
- {
- assert(false);
- m_minVal = m_maxVal = 0;
- m_numberOfClassesCanBeChanged = false;
- }
- setColorScheme(USE_SF_SCALE);
- setNumberOfClasses(initialNumberOfClasses);
- };
- void ccHistogramWindow::fromBinArray( const std::vector<unsigned>& histoValues,
- ccScalarField* sf )
- {
- try
- {
- m_histoValues = histoValues;
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Warning("[ccHistogramWindow::fromBinArray] Not enough memory!");
- return;
- }
- if (sf && m_associatedSF != sf)
- {
- if (m_associatedSF)
- m_associatedSF->release();
- m_associatedSF = sf;
- if (m_associatedSF)
- m_associatedSF->link();
- }
- m_minVal = m_associatedSF ? m_associatedSF->getMin() : 0;
- m_maxVal = m_associatedSF ? m_associatedSF->getMax() : 0;
- m_numberOfClassesCanBeChanged = false;
- //update max histogram value
- m_maxHistoVal = getMaxHistoVal();
- setColorScheme(USE_SF_SCALE);
- }
- void ccHistogramWindow::fromBinArray( const std::vector<unsigned>& histoValues,
- double minVal,
- double maxVal)
- {
- try
- {
- m_histoValues = histoValues;
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Warning("[ccHistogramWindow::fromBinArray] Not enough memory!");
- return;
- }
- m_minVal = minVal;
- m_maxVal = maxVal;
- m_numberOfClassesCanBeChanged = false;
- //update max histogram value
- m_maxHistoVal = getMaxHistoVal();
- }
- void ccHistogramWindow::setCurveValues(const std::vector<double>& curveValues)
- {
- try
- {
- m_curveValues = curveValues;
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Warning("[ccHistogramWindow::setCurveValues] Not enough memory!");
- }
- }
- bool ccHistogramWindow::computeBinArrayFromSF(size_t binCount)
- {
- //clear any existing histogram
- m_histoValues.resize(0);
- if (!m_associatedSF)
- {
- assert(false);
- ccLog::Error("[ccHistogramWindow::computeBinArrayFromSF] Need an associated SF!");
- return false;
- }
- if (binCount == 0)
- {
- assert(false);
- ccLog::Error("[ccHistogramWindow::computeBinArrayFromSF] Invalid number of classes!");
- return false;
- }
- //shortcut: same number of classes than the SF own histogram!
- if (binCount == m_associatedSF->getHistogram().size())
- {
- try
- {
- m_histoValues = m_associatedSF->getHistogram();
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Warning("[ccHistogramWindow::computeBinArrayFromSF] Not enough memory!");
- return false;
- }
- return true;
- }
- //(try to) create new array
- try
- {
- m_histoValues.resize(binCount, 0);
- }
- catch (const std::bad_alloc&)
- {
- ccLog::Warning("[ccHistogramWindow::computeBinArrayFromSF] Not enough memory!");
- return false;
- }
- double range = m_maxVal - m_minVal;
- if (range > 0.0)
- {
- unsigned count = m_associatedSF->currentSize();
- double step = range / binCount;
- for (unsigned i = 0; i < count; ++i)
- {
- double val = m_associatedSF->getValue(i);
- //we ignore values outside of [m_minVal,m_maxVal] (works fro NaN values as well)
- if (/*ccScalarField::ValidValue(val) &&*/val >= m_minVal && val <= m_maxVal)
- {
- size_t bin = static_cast<size_t>((val - m_minVal) / step); //static_cast is equivalent to floor if value >= 0
- ++m_histoValues[std::min(bin, binCount - 1)];
- }
- }
- }
- else
- {
- m_histoValues[0] = m_associatedSF->currentSize();
- }
- return true;
- }
- unsigned ccHistogramWindow::getMaxHistoVal()
- {
- unsigned m_maxHistoVal = 0;
- for (size_t i = 0; i < m_histoValues.size(); ++i)
- {
- m_maxHistoVal = std::max(m_maxHistoVal, m_histoValues[i]);
- }
- return m_maxHistoVal;
- }
- void ccHistogramWindow::setNumberOfClasses(size_t n)
- {
- if (n == 0)
- {
- //invalid parameter
- assert(false);
- return;
- }
- if (n == m_histoValues.size())
- {
- //nothing to do
- return;
- }
- if (m_associatedSF)
- {
- //dynamically recompute histogram values
- computeBinArrayFromSF(n);
- }
- //update max histogram value
- m_maxHistoVal = getMaxHistoVal();
- }
- void ccHistogramWindow::refreshBars()
- {
- if ( m_histogram
- && m_colorScheme == USE_SF_SCALE
- && m_associatedSF
- && m_associatedSF->getColorScale())
- {
- int histoSize = static_cast<int>(m_histoValues.size());
- //DGM: the bars will be redrawn only if we delete and recreate the graph?!
- m_histogram->clearData();
- QVector<double> keyData(histoSize);
- QVector<double> valueData(histoSize);
- QVector<QColor> colors(histoSize);
- for (int i = 0; i < histoSize; ++i)
- {
- //we take the 'normalized' value at the middle of the class
- double normVal = (i + 0.5) / histoSize;
- keyData[i] = m_minVal + normVal * (m_maxVal - m_minVal);
- valueData[i] = m_histoValues[i];
- const ccColor::Rgb* col = m_associatedSF->getColor(static_cast<ScalarType>(keyData[i]));
- if (!col) //hidden values may have no associated color!
- col = &ccColor::lightGreyRGB;
- colors[i] = QColor(col->r, col->g, col->b);
- }
- m_histogram->setData(keyData, valueData, colors);
- //rescaleAxes();
- }
- replot(QCustomPlot::rpImmediateRefresh);
- }
- void ccHistogramWindow::setSFInteractionMode(SFInteractionModes modes)
- {
- m_sfInteractionModes = modes;
- }
- void ccHistogramWindow::setAxisDisplayOption(AxisDisplayOptions axisOptions)
- {
- m_axisDisplayOptions = axisOptions;
- }
- void ccHistogramWindow::setRefreshAfterResize(bool refreshAfterResize)
- {
- m_refreshAfterResize = refreshAfterResize;
- }
- void ccHistogramWindow::refresh()
- {
- // set ranges appropriate to show data
- double minVal = m_minVal;
- double maxVal = m_maxVal;
- if (m_sfInteractionModes && m_associatedSF)
- {
- double minSat = m_associatedSF->saturationRange().min();
- double maxSat = m_associatedSF->saturationRange().max();
- minVal = std::min(minVal, minSat);
- maxVal = std::max(maxVal, maxSat);
- }
- xAxis->setRange(minVal, std::max(minVal + std::numeric_limits<ScalarType>::epsilon(), maxVal));
- yAxis->setRange(0, m_maxHistoVal);
- xAxis->setVisible(m_axisDisplayOptions.testFlag(AxisDisplayOption::XAxis));
- yAxis->setVisible(m_axisDisplayOptions.testFlag(AxisDisplayOption::YAxis));
-
- if (!m_titleStr.isEmpty())
- {
- // add title layout element
- if (!m_titlePlot)
- {
- //add a row for the title
- plotLayout()->insertRow(0);
- }
- else
- {
- //remove previous title
- plotLayout()->remove(m_titlePlot);
- m_titlePlot = nullptr;
- }
- m_titlePlot = new QCPTextElement(this, QStringLiteral("%0 [%1 classes]").arg(m_titleStr, QString::number(m_histoValues.size())));
- //title font
- m_renderingFont.setPointSize(ccGui::Parameters().defaultFontSize);
- m_titlePlot->setFont(m_renderingFont);
- plotLayout()->addElement(0, 0, m_titlePlot);
- }
- //clear previous display
- m_histogram = nullptr;
- m_vertBar = nullptr;
- m_overlayCurve = nullptr;
- m_areaLeft = nullptr;
- m_areaRight = nullptr;
- m_arrowLeft = nullptr;
- m_arrowRight = nullptr;
- this->clearGraphs();
- this->clearPlottables();
- if (m_histoValues.empty())
- return;
- //default color scale to be used for display
- ccColorScale::Shared colorScale = (m_colorScale ? m_colorScale : ccColorScalesManager::GetDefaultScale());
- //histogram
- int histoSize = static_cast<int>(m_histoValues.size());
- double totalSum = 0;
- double partialSum = 0;
- if (histoSize > 0)
- {
- m_histogram = new QCPColoredBars(xAxis, yAxis);
- m_histogram->setWidth((m_maxVal - m_minVal) / histoSize);
- m_histogram->setAntialiased(false);
- m_histogram->setAntialiasedFill(false);
-
- QVector<double> keyData(histoSize);
- QVector<double> valueData(histoSize);
- HISTOGRAM_COLOR_SCHEME colorScheme = m_colorScheme;
- switch (colorScheme)
- {
- case USE_SOLID_COLOR:
- m_histogram->setBrush(QBrush(m_solidColor, Qt::SolidPattern));
- m_histogram->setPen(QPen(m_solidColor));
- break;
- case USE_CUSTOM_COLOR_SCALE:
- //nothing to do
- break;
- case USE_SF_SCALE:
- if (m_associatedSF && m_associatedSF->getColorScale())
- {
- //we use the SF's color scale
- colorScale = m_associatedSF->getColorScale();
- }
- else
- {
- //we'll use the default one...
- assert(false);
- colorScheme = USE_CUSTOM_COLOR_SCALE;
- }
- break;
- default:
- assert(false);
- colorScheme = USE_CUSTOM_COLOR_SCALE;
- break;
- }
- QVector<QColor> colors;
- if (colorScheme != USE_SOLID_COLOR)
- {
- colors.resize(histoSize);
- }
- for (int i = 0; i < histoSize; ++i)
- {
- //we take the 'normalized' value at the middle of the class
- double normVal = (i + 0.5) / histoSize;
- totalSum += m_histoValues[i];
- if (normVal < m_verticalIndicatorPositionPercent)
- {
- partialSum += m_histoValues[i];
- }
- keyData[i] = m_minVal + normVal * (m_maxVal - m_minVal);
- valueData[i] = m_histoValues[i];
- //import color for the current bin
- if (colorScheme != USE_SOLID_COLOR)
- {
- const ccColor::Rgb* col = nullptr;
- if (colorScheme == USE_SF_SCALE)
- {
- //equivalent SF value
- assert(m_associatedSF);
- col = m_associatedSF->getColor(static_cast<ScalarType>(keyData[i]));
- }
- else if (colorScheme == USE_CUSTOM_COLOR_SCALE)
- {
- //use default gradient
- assert(colorScale);
- if (colorScale->isRelative())
- {
- col = colorScale->getColorByRelativePos(normVal);
- }
- else
- {
- double relativePos = colorScale->getRelativePosition(keyData[i]);
- col = colorScale->getColorByRelativePos(std::max(0.0,std::min(1.0, relativePos)));
- }
- }
- if (!col) //hidden values may have no associated color!
- {
- col = &ccColor::lightGreyRGB;
- }
- colors[i] = QColor(col->r, col->g, col->b);
- }
- }
- if (!colors.isEmpty())
- {
- m_histogram->setData(keyData, valueData, colors);
- }
- else
- {
- m_histogram->setData(keyData, valueData);
- }
- }
- //overlay curve?
- int curveSize = static_cast<int>(m_curveValues.size());
- if (curveSize > 1)
- {
- QVector<double> x(curveSize);
- QVector<double> y(curveSize);
- double step = (m_maxVal - m_minVal) / (curveSize - 1);
- for (int i = 0; i < curveSize; ++i)
- {
- x[i] = m_minVal + (i/* + 0.5*/) * step;
- y[i] = m_curveValues[i];
- }
- // create graph and assign data to it:
- m_overlayCurve = addGraph();
- m_overlayCurve->setData(x, y);
- m_overlayCurve->setName("OverlayCurve");
- //set pen color
- const ccColor::Rgb& col = ccColor::darkGrey;
- QPen pen(QColor(col.r, col.g, col.b));
- m_overlayCurve->setPen(pen);
- //set width
- updateOverlayCurveWidth(rect().width(), rect().height());
- }
- //sf interaction mode
- if (m_sfInteractionModes && m_associatedSF)
- {
- if ( m_sfInteractionModes.testFlag(SFInteractionMode::DisplayRange) )
- {
- const ccScalarField::Range& dispRange = m_associatedSF->displayRange();
-
- m_areaLeft = new QCPHiddenArea(true, xAxis, yAxis);
- m_areaLeft->setRange(dispRange.min(), dispRange.max());
- m_areaLeft->setCurrentVal(!std::isnan(m_areaLeftlastValue) ? m_areaLeftlastValue : dispRange.start());
-
- m_areaRight = new QCPHiddenArea(false, xAxis, yAxis);
- m_areaRight->setRange(dispRange.min(), dispRange.max());
- m_areaRight->setCurrentVal(!std::isnan(m_areaRightlastValue) ? m_areaRightlastValue : dispRange.stop());
- }
- if ( m_sfInteractionModes.testFlag(SFInteractionMode::SaturationRange) )
- {
- const ccScalarField::Range& satRange = m_associatedSF->saturationRange();
-
- m_arrowLeft = new QCPArrow(xAxis, yAxis);
- m_arrowLeft->setRange(satRange.min(), satRange.max());
- m_arrowLeft->setCurrentVal(!std::isnan(m_arrowLeftlastValue) ? m_arrowLeftlastValue : satRange.start());
- if (colorScale)
- {
- const ccColor::Rgb* col = colorScale->getColorByRelativePos(m_associatedSF->symmetricalScale() ? 0.5 : 0, m_associatedSF->getColorRampSteps());
- if (col)
- {
- m_arrowLeft->setColor(col->r, col->g, col->b);
- }
- }
-
- m_arrowRight = new QCPArrow(xAxis, yAxis);
- m_arrowRight->setRange(satRange.min(), satRange.max());
- m_arrowRight->setCurrentVal(!std::isnan(m_arrowRightlastValue) ? m_arrowRightlastValue : satRange.stop());
- if (colorScale)
- {
- const ccColor::Rgb* col = colorScale->getColorByRelativePos(1.0, m_associatedSF->getColorRampSteps());
- if (col)
- {
- m_arrowRight->setColor(col->r, col->g, col->b);
- }
- }
- }
- }
- else if (m_drawVerticalIndicator) //vertical hint
- {
- m_vertBar = new QCPBarsWithText(xAxis, yAxis);
- // now we can modify properties of vertBar
- m_vertBar->setName("VertLine");
- m_vertBar->setWidth(0/*(m_maxVal - m_minVal) / histoSize*/);
- m_vertBar->setBrush(QBrush(Qt::red));
- m_vertBar->setPen(QPen(Qt::red));
- m_vertBar->setAntialiasedFill(false);
- QVector<double> keyData(1);
- QVector<double> valueData(1);
- //horizontal position
- keyData[0] = m_minVal + (m_maxVal - m_minVal) * m_verticalIndicatorPositionPercent;
- valueData[0] = m_maxHistoVal;
- m_vertBar->setData(keyData, valueData);
- //precision (same as color scale)
- int precision = static_cast<int>(ccGui::Parameters().displayedNumPrecision);
- unsigned bin = static_cast<unsigned>(m_verticalIndicatorPositionPercent * m_histoValues.size());
- QString valueStr = QString("bin %0").arg(bin);
- m_vertBar->setText(valueStr);
- valueStr = QString("< %0 %").arg((100.0 * partialSum) / totalSum, 0, 'f', 3);
- m_vertBar->appendText(valueStr);
- valueStr = QString("val = %0").arg(m_minVal + (m_maxVal - m_minVal)*m_verticalIndicatorPositionPercent, 0, 'f', precision);
- m_vertBar->appendText(valueStr);
- m_vertBar->setTextAlignment(m_verticalIndicatorPositionPercent > 0.5);
- }
- //rescaleAxes();
- // redraw
- replot();
- }
- void ccHistogramWindow::setMinDispValue(double val)
- {
- m_areaLeftlastValue = val;
- if (m_areaLeft && m_areaLeft->currentVal() != val)
- {
- m_areaLeft->setCurrentVal(val);
- if (m_associatedSF)
- {
- //auto-update
- m_associatedSF->setMinDisplayed(static_cast<ScalarType>(val));
- refreshBars();
- }
- else
- {
- replot();
- }
- Q_EMIT sfMinDispValChanged(val);
- }
- }
- void ccHistogramWindow::setMaxDispValue(double val)
- {
- m_areaRightlastValue = val;
- if (m_areaRight && m_areaRight->currentVal() != val)
- {
- m_areaRight->setCurrentVal(val);
- if (m_associatedSF)
- {
- //auto-update
- m_associatedSF->setMaxDisplayed(static_cast<ScalarType>(val));
- refreshBars();
- }
- else
- {
- replot();
- }
- Q_EMIT sfMaxDispValChanged(val);
- }
- }
- void ccHistogramWindow::setMinSatValue(double val)
- {
- m_arrowLeftlastValue = val;
- if (m_arrowLeft && m_arrowLeft->currentVal() != val)
- {
- m_arrowLeft->setCurrentVal(val);
- if (m_associatedSF)
- {
- //auto-update
- m_associatedSF->setSaturationStart(static_cast<ScalarType>(val));
- refreshBars();
- }
- else
- {
- replot();
- }
- Q_EMIT sfMinSatValChanged(val);
- }
- }
- void ccHistogramWindow::setMaxSatValue(double val)
- {
- m_arrowRightlastValue = val;
- if (m_arrowRight && m_arrowRight->currentVal() != val)
- {
- m_arrowRight->setCurrentVal(val);
- if (m_associatedSF)
- {
- //auto-update
- m_associatedSF->setSaturationStop(static_cast<ScalarType>(val));
- refreshBars();
- }
- else
- {
- replot();
- }
- Q_EMIT sfMaxSatValChanged(val);
- }
- }
- void ccHistogramWindow::updateOverlayCurveWidth(int w, int h)
- {
- if (m_overlayCurve)
- {
- int penWidth = std::max(w, h) / 200;
- if (m_overlayCurve->pen().width() != penWidth)
- {
- QPen pen = m_overlayCurve->pen();
- pen.setWidth(penWidth);
- m_overlayCurve->setPen(pen);
- }
- }
- }
- void ccHistogramWindow::resizeEvent(QResizeEvent * event)
- {
- QCustomPlot::resizeEvent(event);
-
- updateOverlayCurveWidth(event->size().width(), event->size().height());
- if (m_refreshAfterResize)
- {
- refresh();
- }
- }
- void ccHistogramWindow::mousePressEvent(QMouseEvent *event)
- {
- m_lastMouseClick = event->pos();
- if (m_sfInteractionModes)
- {
- m_selectedItem = NONE;
- //check greyed areas (circles)
- if ( m_sfInteractionModes.testFlag(SFInteractionMode::DisplayRange) )
- {
- if (m_areaLeft && m_areaLeft->isSelectable(m_lastMouseClick))
- m_selectedItem = LEFT_AREA;
- if (m_areaRight && m_areaRight->isSelectable(m_lastMouseClick))
- {
- if (m_selectedItem == NONE)
- m_selectedItem = RIGHT_AREA;
- else
- m_selectedItem = BOTH_AREAS;
- }
- }
- //check yellow triangles
- if ( m_sfInteractionModes.testFlag(SFInteractionMode::SaturationRange)
- && (m_selectedItem == NONE) )
- {
- if (m_arrowLeft && m_arrowLeft->isSelectable(m_lastMouseClick))
- m_selectedItem = LEFT_ARROW;
- if (m_arrowRight && m_arrowRight->isSelectable(m_lastMouseClick))
- {
- if (m_selectedItem == NONE)
- m_selectedItem = RIGHT_ARROW;
- else
- m_selectedItem = BOTH_ARROWS;
- }
- }
- }
- else
- {
- mouseMoveEvent(event);
- }
- }
- void ccHistogramWindow::mouseMoveEvent(QMouseEvent *event)
- {
- if (event->buttons() & Qt::LeftButton)
- {
- if (m_sfInteractionModes)
- {
- QPoint mousePos = event->pos();
- if (m_histogram)
- {
- QRect rect = m_histogram->rect();
- mousePos.setX(std::min(rect.x() + rect.width(), std::max(rect.x(), mousePos.x())));
- }
- switch (m_selectedItem)
- {
- case NONE:
- //nothing to do
- break;
- case LEFT_AREA:
- if (m_areaLeft)
- {
- double newValue = m_areaLeft->pixelToKey(mousePos.x());
- if (m_areaRight)
- newValue = std::min(newValue, m_areaRight->currentVal());
- setMinDispValue(newValue);
- }
- break;
- case RIGHT_AREA:
- if (m_areaRight)
- {
- double newValue = m_areaRight->pixelToKey(mousePos.x());
- if (m_areaLeft)
- newValue = std::max(newValue, m_areaLeft->currentVal());
- setMaxDispValue(newValue);
- }
- break;
- case BOTH_AREAS:
- {
- int dx = m_lastMouseClick.x() - mousePos.x();
- if (dx < -2)
- {
- //going to the right
- m_selectedItem = RIGHT_AREA;
- //call the same method again
- mouseMoveEvent(event);
- return;
- }
- else if (dx > 2)
- {
- //going to the left
- m_selectedItem = LEFT_AREA;
- //call the same method again
- mouseMoveEvent(event);
- return;
- }
- //else: nothing we can do right now!
- }
- break;
- case LEFT_ARROW:
- if (m_arrowLeft)
- {
- double newValue = m_arrowLeft->pixelToKey(mousePos.x());
- if (m_arrowRight)
- newValue = std::min(newValue, m_arrowRight->currentVal());
- setMinSatValue(newValue);
- }
- break;
- case RIGHT_ARROW:
- if (m_arrowRight)
- {
- double newValue = m_arrowRight->pixelToKey(mousePos.x());
- if (m_arrowLeft)
- newValue = std::max(newValue, m_arrowLeft->currentVal());
- setMaxSatValue(newValue);
- }
- break;
- case BOTH_ARROWS:
- {
- int dx = m_lastMouseClick.x() - mousePos.x();
- if (dx < -2)
- {
- //going to the right
- m_selectedItem = RIGHT_ARROW;
- //call the same method again
- mouseMoveEvent(event);
- return;
- }
- else if (dx > 2)
- {
- //going to the left
- m_selectedItem = LEFT_ARROW;
- //call the same method again
- mouseMoveEvent(event);
- return;
- }
- //else: nothing we can do right now!
- }
- break;
- default:
- assert(false);
- break;
- }
- }
- else
- {
- if (m_histogram && !m_histoValues.empty())
- {
- QRect roi = m_histogram->rect();
- if (roi.contains(event->pos(), false))
- {
- m_drawVerticalIndicator = true;
- int verticalIndicatorPosition = (static_cast<int>(m_histoValues.size()) * (event->x() - roi.x())) / roi.width();
- m_verticalIndicatorPositionPercent = static_cast<double>(verticalIndicatorPosition) / m_histoValues.size();
- refresh();
- }
- }
- }
- }
- else
- {
- event->ignore();
- }
- }
- void ccHistogramWindow::wheelEvent(QWheelEvent* e)
- {
- if (!m_numberOfClassesCanBeChanged)
- {
- e->ignore();
- return;
- }
- if (e->delta() < 0)
- {
- if (m_histoValues.size() > 4)
- {
- setNumberOfClasses(std::max<size_t>(4, m_histoValues.size() - 4));
- refresh();
- }
- }
- else //if (e->delta() > 0)
- {
- setNumberOfClasses(m_histoValues.size() + 4);
- refresh();
- }
- e->accept();
- }
- ccHistogramWindowDlg::ccHistogramWindowDlg(QWidget* parent/*=nullptr*/)
- : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint)
- , m_win(new ccHistogramWindow(this))
- , m_gui(new Ui_HistogramDialog)
- {
- m_gui->setupUi(this);
-
- auto hboxLayout = new QHBoxLayout;
-
- hboxLayout->setContentsMargins(0, 0, 0, 0);
- hboxLayout->addWidget(m_win);
- m_gui->histoFrame->setLayout(hboxLayout);
- connect(m_gui->exportCSVToolButton, &QAbstractButton::clicked, this, &ccHistogramWindowDlg::onExportToCSV);
- connect(m_gui->exportImageToolButton, &QAbstractButton::clicked, this, &ccHistogramWindowDlg::onExportToImage);
- }
- ccHistogramWindowDlg::~ccHistogramWindowDlg()
- {
- delete m_gui;
- }
- //CSV file default separator
- static const QChar s_csvSep(';');
- bool ccHistogramWindowDlg::exportToCSV(QString filename) const
- {
- if (!m_win || m_win->histoValues().empty())
- {
- ccLog::Warning("[Histogram] Histogram has no associated values (can't save file)");
- return false;
- }
- QFile file(filename);
- if (!file.open(QFile::WriteOnly | QFile::Text))
- {
- ccLog::Warning(QString("[Histogram] Failed to save histogram to file '%1'").arg(filename));
- return false;
- }
- QTextStream stream(&file);
- stream.setRealNumberPrecision(12);
- stream.setRealNumberNotation(QTextStream::FixedNotation);
- //header
- stream << "Class; Value; Class start; Class end;" << endl;
- //data
- {
- const std::vector<unsigned>& histoValues = m_win->histoValues();
- int histoSize = static_cast<int>(histoValues.size());
- double step = (m_win->maxVal() - m_win->minVal()) / histoSize;
- for (int i = 0; i < histoSize; ++i)
- {
- double minVal = m_win->minVal() + i*step;
- stream << i + 1; //class index
- stream << s_csvSep;
- stream << histoValues[i]; //class value
- stream << s_csvSep;
- stream << minVal; //min value
- stream << s_csvSep;
- stream << minVal + step; //max value
- stream << s_csvSep;
- stream << endl;
- }
- }
- file.close();
- ccLog::Print(QString("[Histogram] File '%1' saved").arg(filename));
- return true;
- }
- void ccHistogramWindowDlg::onExportToCSV()
- {
- if (!m_win)
- {
- assert(false);
- return;
- }
- //persistent settings
- QSettings settings;
- settings.beginGroup(ccPS::SaveFile());
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- currentPath += QString("/") + m_win->windowTitle() + ".csv";
- //ask for a filename
- QString filename = QFileDialog::getSaveFileName(this, "Select output file", currentPath, "*.csv");
- if (filename.isEmpty())
- {
- //process cancelled by user
- return;
- }
- //save last saving location
- settings.setValue(ccPS::CurrentPath(), QFileInfo(filename).absolutePath());
- settings.endGroup();
- //save file
- exportToCSV(filename);
- }
- void ccHistogramWindowDlg::onExportToImage()
- {
- if (!m_win)
- {
- assert(false);
- return;
- }
- //persistent settings
- QSettings settings;
- settings.beginGroup(ccPS::SaveFile());
- QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
- QString outputFilename = ImageFileFilter::GetSaveFilename("Select output file",
- m_win->windowTitle(),
- currentPath,
- this);
- if (outputFilename.isEmpty())
- {
- //process cancelled by user (or error)
- return;
- }
- //save current export path to persistent settings
- settings.setValue(ccPS::CurrentPath(), QFileInfo(outputFilename).absolutePath());
- settings.endGroup();
- //save the widget as an image file
- QPixmap image = m_win->grab();
- if (image.save(outputFilename))
- {
- ccLog::Print(QString("[Histogram] Image '%1' successfully saved").arg(outputFilename));
- }
- else
- {
- ccLog::Error(QString("Failed to save file '%1'").arg(outputFilename));
- }
- }
|