ccHistogramWindow.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134
  1. //##########################################################################
  2. //# #
  3. //# CLOUDCOMPARE #
  4. //# #
  5. //# This program is free software; you can redistribute it and/or modify #
  6. //# it under the terms of the GNU General Public License as published by #
  7. //# the Free Software Foundation; version 2 or later of the License. #
  8. //# #
  9. //# This program is distributed in the hope that it will be useful, #
  10. //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
  11. //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
  12. //# GNU General Public License for more details. #
  13. //# #
  14. //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
  15. //# #
  16. //##########################################################################
  17. #include "ccHistogramWindow.h"
  18. #include "ccGuiParameters.h"
  19. //Local
  20. #include "ccQCustomPlot.h"
  21. #include "ccPersistentSettings.h"
  22. //qCC_db
  23. #include <ccColorScalesManager.h>
  24. #include <ccFileUtils.h>
  25. //qCC_io
  26. #include <ImageFileFilter.h>
  27. //Qt
  28. #include <QCloseEvent>
  29. #include <QFile>
  30. #include <QTextStream>
  31. #include <QSettings>
  32. #include <QFileDialog>
  33. //System
  34. #include <assert.h>
  35. #include <cmath>
  36. //Gui
  37. #include "ui_histogramDlg.h"
  38. ccHistogramWindow::ccHistogramWindow(QWidget* parent/*=nullptr*/)
  39. : QCustomPlot(parent)
  40. , m_titlePlot(nullptr)
  41. , m_colorScheme(USE_SOLID_COLOR)
  42. , m_solidColor(Qt::blue)
  43. , m_colorScale(ccColorScalesManager::GetDefaultScale())
  44. , m_associatedSF(nullptr)
  45. , m_numberOfClassesCanBeChanged(false)
  46. , m_histogram(nullptr)
  47. , m_minVal(0)
  48. , m_maxVal(0)
  49. , m_maxHistoVal(0)
  50. , m_overlayCurve(nullptr)
  51. , m_vertBar(nullptr)
  52. , m_drawVerticalIndicator(false)
  53. , m_verticalIndicatorPositionPercent(0)
  54. , m_sfInteractionModes(SFInteractionMode::None)
  55. , m_axisDisplayOptions(AxisDisplayOption::All)
  56. , m_selectedItem(NONE)
  57. , m_areaLeft(nullptr)
  58. , m_areaLeftlastValue(std::numeric_limits<double>::quiet_NaN())
  59. , m_areaRight(nullptr)
  60. , m_areaRightlastValue(std::numeric_limits<double>::quiet_NaN())
  61. , m_arrowLeft(nullptr)
  62. , m_arrowLeftlastValue(std::numeric_limits<double>::quiet_NaN())
  63. , m_arrowRight(nullptr)
  64. , m_arrowRightlastValue(std::numeric_limits<double>::quiet_NaN())
  65. , m_lastMouseClick(0, 0)
  66. , m_refreshAfterResize(true)
  67. {
  68. setWindowTitle("Histogram");
  69. setFocusPolicy(Qt::StrongFocus);
  70. setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
  71. setAutoAddPlottableToLegend(false);
  72. setAntialiasedElements(QCP::AntialiasedElement::aeAll);
  73. //default font for text rendering
  74. m_renderingFont.setFamily(QString::fromUtf8("Arial"));
  75. m_renderingFont.setBold(false);
  76. //m_renderingFont.setWeight(75);
  77. // make ticks on bottom axis go outward
  78. assert(xAxis && yAxis);
  79. xAxis->setTickLength(0, 5);
  80. xAxis->setSubTickLength(0, 3);
  81. yAxis->setTickLength(0, 5);
  82. yAxis->setSubTickLength(0, 3);
  83. }
  84. ccHistogramWindow::~ccHistogramWindow()
  85. {
  86. clearInternal();
  87. }
  88. void ccHistogramWindow::clear()
  89. {
  90. clearInternal();
  91. refresh();
  92. }
  93. void ccHistogramWindow::clearInternal()
  94. {
  95. if (m_associatedSF)
  96. {
  97. m_associatedSF->release();
  98. m_associatedSF = nullptr;
  99. }
  100. m_histoValues.resize(0);
  101. m_maxHistoVal = 0;
  102. m_curveValues.resize(0);
  103. m_selectedItem = NONE;
  104. }
  105. void ccHistogramWindow::setTitle(const QString& str)
  106. {
  107. m_titleStr = str;
  108. }
  109. void ccHistogramWindow::setAxisLabels(const QString& xLabel, const QString& yLabel)
  110. {
  111. if (xLabel.isNull())
  112. {
  113. xAxis->setVisible(false);
  114. }
  115. else
  116. {
  117. // set labels
  118. xAxis->setLabel(xLabel);
  119. xAxis->setVisible(true);
  120. }
  121. if (yLabel.isNull())
  122. {
  123. yAxis->setVisible(false);
  124. }
  125. else
  126. {
  127. // set labels
  128. yAxis->setLabel(yLabel);
  129. yAxis->setVisible(true);
  130. }
  131. }
  132. void ccHistogramWindow::fromSF( ccScalarField* sf,
  133. unsigned initialNumberOfClasses/*=0*/,
  134. bool numberOfClassesCanBeChanged/*=true*/,
  135. bool showNaNValuesInGrey/*=true*/)
  136. {
  137. if (sf && m_associatedSF != sf)
  138. {
  139. if (m_associatedSF)
  140. m_associatedSF->release();
  141. m_associatedSF = sf;
  142. if (m_associatedSF)
  143. m_associatedSF->link();
  144. }
  145. if (m_associatedSF)
  146. {
  147. m_minVal = showNaNValuesInGrey ? m_associatedSF->getMin() : m_associatedSF->displayRange().start();
  148. m_maxVal = showNaNValuesInGrey ? m_associatedSF->getMax() : m_associatedSF->displayRange().stop();
  149. m_numberOfClassesCanBeChanged = numberOfClassesCanBeChanged;
  150. }
  151. else
  152. {
  153. assert(false);
  154. m_minVal = m_maxVal = 0;
  155. m_numberOfClassesCanBeChanged = false;
  156. }
  157. setColorScheme(USE_SF_SCALE);
  158. setNumberOfClasses(initialNumberOfClasses);
  159. };
  160. void ccHistogramWindow::fromBinArray( const std::vector<unsigned>& histoValues,
  161. ccScalarField* sf )
  162. {
  163. try
  164. {
  165. m_histoValues = histoValues;
  166. }
  167. catch (const std::bad_alloc&)
  168. {
  169. ccLog::Warning("[ccHistogramWindow::fromBinArray] Not enough memory!");
  170. return;
  171. }
  172. if (sf && m_associatedSF != sf)
  173. {
  174. if (m_associatedSF)
  175. m_associatedSF->release();
  176. m_associatedSF = sf;
  177. if (m_associatedSF)
  178. m_associatedSF->link();
  179. }
  180. m_minVal = m_associatedSF ? m_associatedSF->getMin() : 0;
  181. m_maxVal = m_associatedSF ? m_associatedSF->getMax() : 0;
  182. m_numberOfClassesCanBeChanged = false;
  183. //update max histogram value
  184. m_maxHistoVal = getMaxHistoVal();
  185. setColorScheme(USE_SF_SCALE);
  186. }
  187. void ccHistogramWindow::fromBinArray( const std::vector<unsigned>& histoValues,
  188. double minVal,
  189. double maxVal)
  190. {
  191. try
  192. {
  193. m_histoValues = histoValues;
  194. }
  195. catch (const std::bad_alloc&)
  196. {
  197. ccLog::Warning("[ccHistogramWindow::fromBinArray] Not enough memory!");
  198. return;
  199. }
  200. m_minVal = minVal;
  201. m_maxVal = maxVal;
  202. m_numberOfClassesCanBeChanged = false;
  203. //update max histogram value
  204. m_maxHistoVal = getMaxHistoVal();
  205. }
  206. void ccHistogramWindow::setCurveValues(const std::vector<double>& curveValues)
  207. {
  208. try
  209. {
  210. m_curveValues = curveValues;
  211. }
  212. catch (const std::bad_alloc&)
  213. {
  214. ccLog::Warning("[ccHistogramWindow::setCurveValues] Not enough memory!");
  215. }
  216. }
  217. bool ccHistogramWindow::computeBinArrayFromSF(size_t binCount)
  218. {
  219. //clear any existing histogram
  220. m_histoValues.resize(0);
  221. if (!m_associatedSF)
  222. {
  223. assert(false);
  224. ccLog::Error("[ccHistogramWindow::computeBinArrayFromSF] Need an associated SF!");
  225. return false;
  226. }
  227. if (binCount == 0)
  228. {
  229. assert(false);
  230. ccLog::Error("[ccHistogramWindow::computeBinArrayFromSF] Invalid number of classes!");
  231. return false;
  232. }
  233. //shortcut: same number of classes than the SF own histogram!
  234. if (binCount == m_associatedSF->getHistogram().size())
  235. {
  236. try
  237. {
  238. m_histoValues = m_associatedSF->getHistogram();
  239. }
  240. catch (const std::bad_alloc&)
  241. {
  242. ccLog::Warning("[ccHistogramWindow::computeBinArrayFromSF] Not enough memory!");
  243. return false;
  244. }
  245. return true;
  246. }
  247. //(try to) create new array
  248. try
  249. {
  250. m_histoValues.resize(binCount, 0);
  251. }
  252. catch (const std::bad_alloc&)
  253. {
  254. ccLog::Warning("[ccHistogramWindow::computeBinArrayFromSF] Not enough memory!");
  255. return false;
  256. }
  257. double range = m_maxVal - m_minVal;
  258. if (range > 0.0)
  259. {
  260. unsigned count = m_associatedSF->currentSize();
  261. double step = range / binCount;
  262. for (unsigned i = 0; i < count; ++i)
  263. {
  264. double val = m_associatedSF->getValue(i);
  265. //we ignore values outside of [m_minVal,m_maxVal] (works fro NaN values as well)
  266. if (/*ccScalarField::ValidValue(val) &&*/val >= m_minVal && val <= m_maxVal)
  267. {
  268. size_t bin = static_cast<size_t>((val - m_minVal) / step); //static_cast is equivalent to floor if value >= 0
  269. ++m_histoValues[std::min(bin, binCount - 1)];
  270. }
  271. }
  272. }
  273. else
  274. {
  275. m_histoValues[0] = m_associatedSF->currentSize();
  276. }
  277. return true;
  278. }
  279. unsigned ccHistogramWindow::getMaxHistoVal()
  280. {
  281. unsigned m_maxHistoVal = 0;
  282. for (size_t i = 0; i < m_histoValues.size(); ++i)
  283. {
  284. m_maxHistoVal = std::max(m_maxHistoVal, m_histoValues[i]);
  285. }
  286. return m_maxHistoVal;
  287. }
  288. void ccHistogramWindow::setNumberOfClasses(size_t n)
  289. {
  290. if (n == 0)
  291. {
  292. //invalid parameter
  293. assert(false);
  294. return;
  295. }
  296. if (n == m_histoValues.size())
  297. {
  298. //nothing to do
  299. return;
  300. }
  301. if (m_associatedSF)
  302. {
  303. //dynamically recompute histogram values
  304. computeBinArrayFromSF(n);
  305. }
  306. //update max histogram value
  307. m_maxHistoVal = getMaxHistoVal();
  308. }
  309. void ccHistogramWindow::refreshBars()
  310. {
  311. if ( m_histogram
  312. && m_colorScheme == USE_SF_SCALE
  313. && m_associatedSF
  314. && m_associatedSF->getColorScale())
  315. {
  316. int histoSize = static_cast<int>(m_histoValues.size());
  317. //DGM: the bars will be redrawn only if we delete and recreate the graph?!
  318. m_histogram->clearData();
  319. QVector<double> keyData(histoSize);
  320. QVector<double> valueData(histoSize);
  321. QVector<QColor> colors(histoSize);
  322. for (int i = 0; i < histoSize; ++i)
  323. {
  324. //we take the 'normalized' value at the middle of the class
  325. double normVal = (i + 0.5) / histoSize;
  326. keyData[i] = m_minVal + normVal * (m_maxVal - m_minVal);
  327. valueData[i] = m_histoValues[i];
  328. const ccColor::Rgb* col = m_associatedSF->getColor(static_cast<ScalarType>(keyData[i]));
  329. if (!col) //hidden values may have no associated color!
  330. col = &ccColor::lightGreyRGB;
  331. colors[i] = QColor(col->r, col->g, col->b);
  332. }
  333. m_histogram->setData(keyData, valueData, colors);
  334. //rescaleAxes();
  335. }
  336. replot(QCustomPlot::rpImmediateRefresh);
  337. }
  338. void ccHistogramWindow::setSFInteractionMode(SFInteractionModes modes)
  339. {
  340. m_sfInteractionModes = modes;
  341. }
  342. void ccHistogramWindow::setAxisDisplayOption(AxisDisplayOptions axisOptions)
  343. {
  344. m_axisDisplayOptions = axisOptions;
  345. }
  346. void ccHistogramWindow::setRefreshAfterResize(bool refreshAfterResize)
  347. {
  348. m_refreshAfterResize = refreshAfterResize;
  349. }
  350. void ccHistogramWindow::refresh()
  351. {
  352. // set ranges appropriate to show data
  353. double minVal = m_minVal;
  354. double maxVal = m_maxVal;
  355. if (m_sfInteractionModes && m_associatedSF)
  356. {
  357. double minSat = m_associatedSF->saturationRange().min();
  358. double maxSat = m_associatedSF->saturationRange().max();
  359. minVal = std::min(minVal, minSat);
  360. maxVal = std::max(maxVal, maxSat);
  361. }
  362. xAxis->setRange(minVal, std::max(minVal + std::numeric_limits<ScalarType>::epsilon(), maxVal));
  363. yAxis->setRange(0, m_maxHistoVal);
  364. xAxis->setVisible(m_axisDisplayOptions.testFlag(AxisDisplayOption::XAxis));
  365. yAxis->setVisible(m_axisDisplayOptions.testFlag(AxisDisplayOption::YAxis));
  366. if (!m_titleStr.isEmpty())
  367. {
  368. // add title layout element
  369. if (!m_titlePlot)
  370. {
  371. //add a row for the title
  372. plotLayout()->insertRow(0);
  373. }
  374. else
  375. {
  376. //remove previous title
  377. plotLayout()->remove(m_titlePlot);
  378. m_titlePlot = nullptr;
  379. }
  380. m_titlePlot = new QCPTextElement(this, QStringLiteral("%0 [%1 classes]").arg(m_titleStr, QString::number(m_histoValues.size())));
  381. //title font
  382. m_renderingFont.setPointSize(ccGui::Parameters().defaultFontSize);
  383. m_titlePlot->setFont(m_renderingFont);
  384. plotLayout()->addElement(0, 0, m_titlePlot);
  385. }
  386. //clear previous display
  387. m_histogram = nullptr;
  388. m_vertBar = nullptr;
  389. m_overlayCurve = nullptr;
  390. m_areaLeft = nullptr;
  391. m_areaRight = nullptr;
  392. m_arrowLeft = nullptr;
  393. m_arrowRight = nullptr;
  394. this->clearGraphs();
  395. this->clearPlottables();
  396. if (m_histoValues.empty())
  397. return;
  398. //default color scale to be used for display
  399. ccColorScale::Shared colorScale = (m_colorScale ? m_colorScale : ccColorScalesManager::GetDefaultScale());
  400. //histogram
  401. int histoSize = static_cast<int>(m_histoValues.size());
  402. double totalSum = 0;
  403. double partialSum = 0;
  404. if (histoSize > 0)
  405. {
  406. m_histogram = new QCPColoredBars(xAxis, yAxis);
  407. m_histogram->setWidth((m_maxVal - m_minVal) / histoSize);
  408. m_histogram->setAntialiased(false);
  409. m_histogram->setAntialiasedFill(false);
  410. QVector<double> keyData(histoSize);
  411. QVector<double> valueData(histoSize);
  412. HISTOGRAM_COLOR_SCHEME colorScheme = m_colorScheme;
  413. switch (colorScheme)
  414. {
  415. case USE_SOLID_COLOR:
  416. m_histogram->setBrush(QBrush(m_solidColor, Qt::SolidPattern));
  417. m_histogram->setPen(QPen(m_solidColor));
  418. break;
  419. case USE_CUSTOM_COLOR_SCALE:
  420. //nothing to do
  421. break;
  422. case USE_SF_SCALE:
  423. if (m_associatedSF && m_associatedSF->getColorScale())
  424. {
  425. //we use the SF's color scale
  426. colorScale = m_associatedSF->getColorScale();
  427. }
  428. else
  429. {
  430. //we'll use the default one...
  431. assert(false);
  432. colorScheme = USE_CUSTOM_COLOR_SCALE;
  433. }
  434. break;
  435. default:
  436. assert(false);
  437. colorScheme = USE_CUSTOM_COLOR_SCALE;
  438. break;
  439. }
  440. QVector<QColor> colors;
  441. if (colorScheme != USE_SOLID_COLOR)
  442. {
  443. colors.resize(histoSize);
  444. }
  445. for (int i = 0; i < histoSize; ++i)
  446. {
  447. //we take the 'normalized' value at the middle of the class
  448. double normVal = (i + 0.5) / histoSize;
  449. totalSum += m_histoValues[i];
  450. if (normVal < m_verticalIndicatorPositionPercent)
  451. {
  452. partialSum += m_histoValues[i];
  453. }
  454. keyData[i] = m_minVal + normVal * (m_maxVal - m_minVal);
  455. valueData[i] = m_histoValues[i];
  456. //import color for the current bin
  457. if (colorScheme != USE_SOLID_COLOR)
  458. {
  459. const ccColor::Rgb* col = nullptr;
  460. if (colorScheme == USE_SF_SCALE)
  461. {
  462. //equivalent SF value
  463. assert(m_associatedSF);
  464. col = m_associatedSF->getColor(static_cast<ScalarType>(keyData[i]));
  465. }
  466. else if (colorScheme == USE_CUSTOM_COLOR_SCALE)
  467. {
  468. //use default gradient
  469. assert(colorScale);
  470. if (colorScale->isRelative())
  471. {
  472. col = colorScale->getColorByRelativePos(normVal);
  473. }
  474. else
  475. {
  476. double relativePos = colorScale->getRelativePosition(keyData[i]);
  477. col = colorScale->getColorByRelativePos(std::max(0.0,std::min(1.0, relativePos)));
  478. }
  479. }
  480. if (!col) //hidden values may have no associated color!
  481. {
  482. col = &ccColor::lightGreyRGB;
  483. }
  484. colors[i] = QColor(col->r, col->g, col->b);
  485. }
  486. }
  487. if (!colors.isEmpty())
  488. {
  489. m_histogram->setData(keyData, valueData, colors);
  490. }
  491. else
  492. {
  493. m_histogram->setData(keyData, valueData);
  494. }
  495. }
  496. //overlay curve?
  497. int curveSize = static_cast<int>(m_curveValues.size());
  498. if (curveSize > 1)
  499. {
  500. QVector<double> x(curveSize);
  501. QVector<double> y(curveSize);
  502. double step = (m_maxVal - m_minVal) / (curveSize - 1);
  503. for (int i = 0; i < curveSize; ++i)
  504. {
  505. x[i] = m_minVal + (i/* + 0.5*/) * step;
  506. y[i] = m_curveValues[i];
  507. }
  508. // create graph and assign data to it:
  509. m_overlayCurve = addGraph();
  510. m_overlayCurve->setData(x, y);
  511. m_overlayCurve->setName("OverlayCurve");
  512. //set pen color
  513. const ccColor::Rgb& col = ccColor::darkGrey;
  514. QPen pen(QColor(col.r, col.g, col.b));
  515. m_overlayCurve->setPen(pen);
  516. //set width
  517. updateOverlayCurveWidth(rect().width(), rect().height());
  518. }
  519. //sf interaction mode
  520. if (m_sfInteractionModes && m_associatedSF)
  521. {
  522. if ( m_sfInteractionModes.testFlag(SFInteractionMode::DisplayRange) )
  523. {
  524. const ccScalarField::Range& dispRange = m_associatedSF->displayRange();
  525. m_areaLeft = new QCPHiddenArea(true, xAxis, yAxis);
  526. m_areaLeft->setRange(dispRange.min(), dispRange.max());
  527. m_areaLeft->setCurrentVal(!std::isnan(m_areaLeftlastValue) ? m_areaLeftlastValue : dispRange.start());
  528. m_areaRight = new QCPHiddenArea(false, xAxis, yAxis);
  529. m_areaRight->setRange(dispRange.min(), dispRange.max());
  530. m_areaRight->setCurrentVal(!std::isnan(m_areaRightlastValue) ? m_areaRightlastValue : dispRange.stop());
  531. }
  532. if ( m_sfInteractionModes.testFlag(SFInteractionMode::SaturationRange) )
  533. {
  534. const ccScalarField::Range& satRange = m_associatedSF->saturationRange();
  535. m_arrowLeft = new QCPArrow(xAxis, yAxis);
  536. m_arrowLeft->setRange(satRange.min(), satRange.max());
  537. m_arrowLeft->setCurrentVal(!std::isnan(m_arrowLeftlastValue) ? m_arrowLeftlastValue : satRange.start());
  538. if (colorScale)
  539. {
  540. const ccColor::Rgb* col = colorScale->getColorByRelativePos(m_associatedSF->symmetricalScale() ? 0.5 : 0, m_associatedSF->getColorRampSteps());
  541. if (col)
  542. {
  543. m_arrowLeft->setColor(col->r, col->g, col->b);
  544. }
  545. }
  546. m_arrowRight = new QCPArrow(xAxis, yAxis);
  547. m_arrowRight->setRange(satRange.min(), satRange.max());
  548. m_arrowRight->setCurrentVal(!std::isnan(m_arrowRightlastValue) ? m_arrowRightlastValue : satRange.stop());
  549. if (colorScale)
  550. {
  551. const ccColor::Rgb* col = colorScale->getColorByRelativePos(1.0, m_associatedSF->getColorRampSteps());
  552. if (col)
  553. {
  554. m_arrowRight->setColor(col->r, col->g, col->b);
  555. }
  556. }
  557. }
  558. }
  559. else if (m_drawVerticalIndicator) //vertical hint
  560. {
  561. m_vertBar = new QCPBarsWithText(xAxis, yAxis);
  562. // now we can modify properties of vertBar
  563. m_vertBar->setName("VertLine");
  564. m_vertBar->setWidth(0/*(m_maxVal - m_minVal) / histoSize*/);
  565. m_vertBar->setBrush(QBrush(Qt::red));
  566. m_vertBar->setPen(QPen(Qt::red));
  567. m_vertBar->setAntialiasedFill(false);
  568. QVector<double> keyData(1);
  569. QVector<double> valueData(1);
  570. //horizontal position
  571. keyData[0] = m_minVal + (m_maxVal - m_minVal) * m_verticalIndicatorPositionPercent;
  572. valueData[0] = m_maxHistoVal;
  573. m_vertBar->setData(keyData, valueData);
  574. //precision (same as color scale)
  575. int precision = static_cast<int>(ccGui::Parameters().displayedNumPrecision);
  576. unsigned bin = static_cast<unsigned>(m_verticalIndicatorPositionPercent * m_histoValues.size());
  577. QString valueStr = QString("bin %0").arg(bin);
  578. m_vertBar->setText(valueStr);
  579. valueStr = QString("< %0 %").arg((100.0 * partialSum) / totalSum, 0, 'f', 3);
  580. m_vertBar->appendText(valueStr);
  581. valueStr = QString("val = %0").arg(m_minVal + (m_maxVal - m_minVal)*m_verticalIndicatorPositionPercent, 0, 'f', precision);
  582. m_vertBar->appendText(valueStr);
  583. m_vertBar->setTextAlignment(m_verticalIndicatorPositionPercent > 0.5);
  584. }
  585. //rescaleAxes();
  586. // redraw
  587. replot();
  588. }
  589. void ccHistogramWindow::setMinDispValue(double val)
  590. {
  591. m_areaLeftlastValue = val;
  592. if (m_areaLeft && m_areaLeft->currentVal() != val)
  593. {
  594. m_areaLeft->setCurrentVal(val);
  595. if (m_associatedSF)
  596. {
  597. //auto-update
  598. m_associatedSF->setMinDisplayed(static_cast<ScalarType>(val));
  599. refreshBars();
  600. }
  601. else
  602. {
  603. replot();
  604. }
  605. Q_EMIT sfMinDispValChanged(val);
  606. }
  607. }
  608. void ccHistogramWindow::setMaxDispValue(double val)
  609. {
  610. m_areaRightlastValue = val;
  611. if (m_areaRight && m_areaRight->currentVal() != val)
  612. {
  613. m_areaRight->setCurrentVal(val);
  614. if (m_associatedSF)
  615. {
  616. //auto-update
  617. m_associatedSF->setMaxDisplayed(static_cast<ScalarType>(val));
  618. refreshBars();
  619. }
  620. else
  621. {
  622. replot();
  623. }
  624. Q_EMIT sfMaxDispValChanged(val);
  625. }
  626. }
  627. void ccHistogramWindow::setMinSatValue(double val)
  628. {
  629. m_arrowLeftlastValue = val;
  630. if (m_arrowLeft && m_arrowLeft->currentVal() != val)
  631. {
  632. m_arrowLeft->setCurrentVal(val);
  633. if (m_associatedSF)
  634. {
  635. //auto-update
  636. m_associatedSF->setSaturationStart(static_cast<ScalarType>(val));
  637. refreshBars();
  638. }
  639. else
  640. {
  641. replot();
  642. }
  643. Q_EMIT sfMinSatValChanged(val);
  644. }
  645. }
  646. void ccHistogramWindow::setMaxSatValue(double val)
  647. {
  648. m_arrowRightlastValue = val;
  649. if (m_arrowRight && m_arrowRight->currentVal() != val)
  650. {
  651. m_arrowRight->setCurrentVal(val);
  652. if (m_associatedSF)
  653. {
  654. //auto-update
  655. m_associatedSF->setSaturationStop(static_cast<ScalarType>(val));
  656. refreshBars();
  657. }
  658. else
  659. {
  660. replot();
  661. }
  662. Q_EMIT sfMaxSatValChanged(val);
  663. }
  664. }
  665. void ccHistogramWindow::updateOverlayCurveWidth(int w, int h)
  666. {
  667. if (m_overlayCurve)
  668. {
  669. int penWidth = std::max(w, h) / 200;
  670. if (m_overlayCurve->pen().width() != penWidth)
  671. {
  672. QPen pen = m_overlayCurve->pen();
  673. pen.setWidth(penWidth);
  674. m_overlayCurve->setPen(pen);
  675. }
  676. }
  677. }
  678. void ccHistogramWindow::resizeEvent(QResizeEvent * event)
  679. {
  680. QCustomPlot::resizeEvent(event);
  681. updateOverlayCurveWidth(event->size().width(), event->size().height());
  682. if (m_refreshAfterResize)
  683. {
  684. refresh();
  685. }
  686. }
  687. void ccHistogramWindow::mousePressEvent(QMouseEvent *event)
  688. {
  689. m_lastMouseClick = event->pos();
  690. if (m_sfInteractionModes)
  691. {
  692. m_selectedItem = NONE;
  693. //check greyed areas (circles)
  694. if ( m_sfInteractionModes.testFlag(SFInteractionMode::DisplayRange) )
  695. {
  696. if (m_areaLeft && m_areaLeft->isSelectable(m_lastMouseClick))
  697. m_selectedItem = LEFT_AREA;
  698. if (m_areaRight && m_areaRight->isSelectable(m_lastMouseClick))
  699. {
  700. if (m_selectedItem == NONE)
  701. m_selectedItem = RIGHT_AREA;
  702. else
  703. m_selectedItem = BOTH_AREAS;
  704. }
  705. }
  706. //check yellow triangles
  707. if ( m_sfInteractionModes.testFlag(SFInteractionMode::SaturationRange)
  708. && (m_selectedItem == NONE) )
  709. {
  710. if (m_arrowLeft && m_arrowLeft->isSelectable(m_lastMouseClick))
  711. m_selectedItem = LEFT_ARROW;
  712. if (m_arrowRight && m_arrowRight->isSelectable(m_lastMouseClick))
  713. {
  714. if (m_selectedItem == NONE)
  715. m_selectedItem = RIGHT_ARROW;
  716. else
  717. m_selectedItem = BOTH_ARROWS;
  718. }
  719. }
  720. }
  721. else
  722. {
  723. mouseMoveEvent(event);
  724. }
  725. }
  726. void ccHistogramWindow::mouseMoveEvent(QMouseEvent *event)
  727. {
  728. if (event->buttons() & Qt::LeftButton)
  729. {
  730. if (m_sfInteractionModes)
  731. {
  732. QPoint mousePos = event->pos();
  733. if (m_histogram)
  734. {
  735. QRect rect = m_histogram->rect();
  736. mousePos.setX(std::min(rect.x() + rect.width(), std::max(rect.x(), mousePos.x())));
  737. }
  738. switch (m_selectedItem)
  739. {
  740. case NONE:
  741. //nothing to do
  742. break;
  743. case LEFT_AREA:
  744. if (m_areaLeft)
  745. {
  746. double newValue = m_areaLeft->pixelToKey(mousePos.x());
  747. if (m_areaRight)
  748. newValue = std::min(newValue, m_areaRight->currentVal());
  749. setMinDispValue(newValue);
  750. }
  751. break;
  752. case RIGHT_AREA:
  753. if (m_areaRight)
  754. {
  755. double newValue = m_areaRight->pixelToKey(mousePos.x());
  756. if (m_areaLeft)
  757. newValue = std::max(newValue, m_areaLeft->currentVal());
  758. setMaxDispValue(newValue);
  759. }
  760. break;
  761. case BOTH_AREAS:
  762. {
  763. int dx = m_lastMouseClick.x() - mousePos.x();
  764. if (dx < -2)
  765. {
  766. //going to the right
  767. m_selectedItem = RIGHT_AREA;
  768. //call the same method again
  769. mouseMoveEvent(event);
  770. return;
  771. }
  772. else if (dx > 2)
  773. {
  774. //going to the left
  775. m_selectedItem = LEFT_AREA;
  776. //call the same method again
  777. mouseMoveEvent(event);
  778. return;
  779. }
  780. //else: nothing we can do right now!
  781. }
  782. break;
  783. case LEFT_ARROW:
  784. if (m_arrowLeft)
  785. {
  786. double newValue = m_arrowLeft->pixelToKey(mousePos.x());
  787. if (m_arrowRight)
  788. newValue = std::min(newValue, m_arrowRight->currentVal());
  789. setMinSatValue(newValue);
  790. }
  791. break;
  792. case RIGHT_ARROW:
  793. if (m_arrowRight)
  794. {
  795. double newValue = m_arrowRight->pixelToKey(mousePos.x());
  796. if (m_arrowLeft)
  797. newValue = std::max(newValue, m_arrowLeft->currentVal());
  798. setMaxSatValue(newValue);
  799. }
  800. break;
  801. case BOTH_ARROWS:
  802. {
  803. int dx = m_lastMouseClick.x() - mousePos.x();
  804. if (dx < -2)
  805. {
  806. //going to the right
  807. m_selectedItem = RIGHT_ARROW;
  808. //call the same method again
  809. mouseMoveEvent(event);
  810. return;
  811. }
  812. else if (dx > 2)
  813. {
  814. //going to the left
  815. m_selectedItem = LEFT_ARROW;
  816. //call the same method again
  817. mouseMoveEvent(event);
  818. return;
  819. }
  820. //else: nothing we can do right now!
  821. }
  822. break;
  823. default:
  824. assert(false);
  825. break;
  826. }
  827. }
  828. else
  829. {
  830. if (m_histogram && !m_histoValues.empty())
  831. {
  832. QRect roi = m_histogram->rect();
  833. if (roi.contains(event->pos(), false))
  834. {
  835. m_drawVerticalIndicator = true;
  836. int verticalIndicatorPosition = (static_cast<int>(m_histoValues.size()) * (event->x() - roi.x())) / roi.width();
  837. m_verticalIndicatorPositionPercent = static_cast<double>(verticalIndicatorPosition) / m_histoValues.size();
  838. refresh();
  839. }
  840. }
  841. }
  842. }
  843. else
  844. {
  845. event->ignore();
  846. }
  847. }
  848. void ccHistogramWindow::wheelEvent(QWheelEvent* e)
  849. {
  850. if (!m_numberOfClassesCanBeChanged)
  851. {
  852. e->ignore();
  853. return;
  854. }
  855. if (e->delta() < 0)
  856. {
  857. if (m_histoValues.size() > 4)
  858. {
  859. setNumberOfClasses(std::max<size_t>(4, m_histoValues.size() - 4));
  860. refresh();
  861. }
  862. }
  863. else //if (e->delta() > 0)
  864. {
  865. setNumberOfClasses(m_histoValues.size() + 4);
  866. refresh();
  867. }
  868. e->accept();
  869. }
  870. ccHistogramWindowDlg::ccHistogramWindowDlg(QWidget* parent/*=nullptr*/)
  871. : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint)
  872. , m_win(new ccHistogramWindow(this))
  873. , m_gui(new Ui_HistogramDialog)
  874. {
  875. m_gui->setupUi(this);
  876. auto hboxLayout = new QHBoxLayout;
  877. hboxLayout->setContentsMargins(0, 0, 0, 0);
  878. hboxLayout->addWidget(m_win);
  879. m_gui->histoFrame->setLayout(hboxLayout);
  880. connect(m_gui->exportCSVToolButton, &QAbstractButton::clicked, this, &ccHistogramWindowDlg::onExportToCSV);
  881. connect(m_gui->exportImageToolButton, &QAbstractButton::clicked, this, &ccHistogramWindowDlg::onExportToImage);
  882. }
  883. ccHistogramWindowDlg::~ccHistogramWindowDlg()
  884. {
  885. delete m_gui;
  886. }
  887. //CSV file default separator
  888. static const QChar s_csvSep(';');
  889. bool ccHistogramWindowDlg::exportToCSV(QString filename) const
  890. {
  891. if (!m_win || m_win->histoValues().empty())
  892. {
  893. ccLog::Warning("[Histogram] Histogram has no associated values (can't save file)");
  894. return false;
  895. }
  896. QFile file(filename);
  897. if (!file.open(QFile::WriteOnly | QFile::Text))
  898. {
  899. ccLog::Warning(QString("[Histogram] Failed to save histogram to file '%1'").arg(filename));
  900. return false;
  901. }
  902. QTextStream stream(&file);
  903. stream.setRealNumberPrecision(12);
  904. stream.setRealNumberNotation(QTextStream::FixedNotation);
  905. //header
  906. stream << "Class; Value; Class start; Class end;" << endl;
  907. //data
  908. {
  909. const std::vector<unsigned>& histoValues = m_win->histoValues();
  910. int histoSize = static_cast<int>(histoValues.size());
  911. double step = (m_win->maxVal() - m_win->minVal()) / histoSize;
  912. for (int i = 0; i < histoSize; ++i)
  913. {
  914. double minVal = m_win->minVal() + i*step;
  915. stream << i + 1; //class index
  916. stream << s_csvSep;
  917. stream << histoValues[i]; //class value
  918. stream << s_csvSep;
  919. stream << minVal; //min value
  920. stream << s_csvSep;
  921. stream << minVal + step; //max value
  922. stream << s_csvSep;
  923. stream << endl;
  924. }
  925. }
  926. file.close();
  927. ccLog::Print(QString("[Histogram] File '%1' saved").arg(filename));
  928. return true;
  929. }
  930. void ccHistogramWindowDlg::onExportToCSV()
  931. {
  932. if (!m_win)
  933. {
  934. assert(false);
  935. return;
  936. }
  937. //persistent settings
  938. QSettings settings;
  939. settings.beginGroup(ccPS::SaveFile());
  940. QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
  941. currentPath += QString("/") + m_win->windowTitle() + ".csv";
  942. //ask for a filename
  943. QString filename = QFileDialog::getSaveFileName(this, "Select output file", currentPath, "*.csv");
  944. if (filename.isEmpty())
  945. {
  946. //process cancelled by user
  947. return;
  948. }
  949. //save last saving location
  950. settings.setValue(ccPS::CurrentPath(), QFileInfo(filename).absolutePath());
  951. settings.endGroup();
  952. //save file
  953. exportToCSV(filename);
  954. }
  955. void ccHistogramWindowDlg::onExportToImage()
  956. {
  957. if (!m_win)
  958. {
  959. assert(false);
  960. return;
  961. }
  962. //persistent settings
  963. QSettings settings;
  964. settings.beginGroup(ccPS::SaveFile());
  965. QString currentPath = settings.value(ccPS::CurrentPath(), ccFileUtils::defaultDocPath()).toString();
  966. QString outputFilename = ImageFileFilter::GetSaveFilename("Select output file",
  967. m_win->windowTitle(),
  968. currentPath,
  969. this);
  970. if (outputFilename.isEmpty())
  971. {
  972. //process cancelled by user (or error)
  973. return;
  974. }
  975. //save current export path to persistent settings
  976. settings.setValue(ccPS::CurrentPath(), QFileInfo(outputFilename).absolutePath());
  977. settings.endGroup();
  978. //save the widget as an image file
  979. QPixmap image = m_win->grab();
  980. if (image.save(outputFilename))
  981. {
  982. ccLog::Print(QString("[Histogram] Image '%1' successfully saved").arg(outputFilename));
  983. }
  984. else
  985. {
  986. ccLog::Error(QString("Failed to save file '%1'").arg(outputFilename));
  987. }
  988. }