QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsrasterhistogramwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrasterhistogramwidget.cpp
3  ---------------------------
4  begin : July 2012
5  copyright : (C) 2012 by Etienne Tourigny
6  email : etourigny dot dev at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsapplication.h"
19 #include "qgisgui.h"
23 
24 #include <QMenu>
25 #include <QFileInfo>
26 #include <QDir>
27 #include <QPainter>
28 #include <QSettings>
29 
30 // QWT Charting widget
31 #include <qwt_global.h>
32 #include <qwt_plot_canvas.h>
33 #include <qwt_legend.h>
34 #include <qwt_plot.h>
35 #include <qwt_plot_curve.h>
36 #include <qwt_plot_grid.h>
37 #include <qwt_plot_marker.h>
38 #include <qwt_plot_picker.h>
39 #include <qwt_picker_machine.h>
40 #include <qwt_plot_zoomer.h>
41 #include <qwt_plot_layout.h>
42 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
43 #include <qwt_plot_renderer.h>
44 #include <qwt_plot_histogram.h>
45 #else
46 #include "qwt5_histogram_item.h"
47 #endif
48 
49 #ifdef WIN32
50 #include <time.h>
51 #endif
52 
53 // this has been removed, now we let the provider/raster interface decide
54 // how many bins are suitable depending on data type and range
55 //#define RASTER_HISTOGRAM_BINS 256
56 
58  : QWidget( parent ),
59  mRasterLayer( lyr ), mRendererWidget( 0 )
60 {
61  setupUi( this );
62 
63  mSaveAsImageButton->setIcon( QgsApplication::getThemeIcon( "/mActionFileSave.svg" ) );
64 
65  mRendererWidget = 0;
66  mRendererName = "singlebandgray";
67 
68  mHistoMin = 0;
69  mHistoMax = 0;
70 
71  mHistoPicker = NULL;
72  mHistoZoomer = NULL;
73  mHistoMarkerMin = NULL;
74  mHistoMarkerMax = NULL;
75 
76  QSettings settings;
77  mHistoShowMarkers = settings.value( "/Raster/histogram/showMarkers", false ).toBool();
78  // mHistoLoadApplyAll = settings.value( "/Raster/histogram/loadApplyAll", false ).toBool();
79  mHistoZoomToMinMax = settings.value( "/Raster/histogram/zoomToMinMax", false ).toBool();
80  mHistoUpdateStyleToMinMax = settings.value( "/Raster/histogram/updateStyleToMinMax", true ).toBool();
81  mHistoDrawLines = settings.value( "/Raster/histogram/drawLines", true ).toBool();
82  // mHistoShowBands = (HistoShowBands) settings.value( "/Raster/histogram/showBands", (int) ShowAll ).toInt();
83  mHistoShowBands = ShowAll;
84 
85  bool isInt = true;
86  if ( true )
87  {
88  //band selector
89  int myBandCountInt = mRasterLayer->bandCount();
90  for ( int myIteratorInt = 1;
91  myIteratorInt <= myBandCountInt;
92  ++myIteratorInt )
93  {
94  cboHistoBand->addItem( mRasterLayer->bandName( myIteratorInt ) );
95  QGis::DataType mySrcDataType = mRasterLayer->dataProvider()->srcDataType( myIteratorInt );
96  if ( !( mySrcDataType == QGis::Byte ||
97  mySrcDataType == QGis::Int16 || mySrcDataType == QGis::Int32 ||
98  mySrcDataType == QGis::UInt16 || mySrcDataType == QGis::UInt32 ) )
99  isInt = false;
100  }
101 
102  // histo min/max selectors
103  leHistoMin->setValidator( new QDoubleValidator( this ) );
104  leHistoMax->setValidator( new QDoubleValidator( this ) );
105  // this might generate many refresh events! test..
106  // connect( leHistoMin, SIGNAL( textChanged( const QString & ) ), this, SLOT( updateHistoMarkers() ) );
107  // connect( leHistoMax, SIGNAL( textChanged( const QString & ) ), this, SLOT( updateHistoMarkers() ) );
108  // connect( leHistoMin, SIGNAL( textChanged( const QString & ) ), this, SLOT( applyHistoMin() ) );
109  // connect( leHistoMax, SIGNAL( textChanged( const QString & ) ), this, SLOT( applyHistoMax() ) );
110  connect( leHistoMin, SIGNAL( editingFinished() ), this, SLOT( applyHistoMin() ) );
111  connect( leHistoMax, SIGNAL( editingFinished() ), this, SLOT( applyHistoMax() ) );
112 
113  // histo actions
114  // TODO move/add options to qgis options dialog
115  QMenu* menu = new QMenu( this );
116  menu->setSeparatorsCollapsible( false );
117  btnHistoActions->setMenu( menu );
118  QActionGroup* group;
119  QAction* action;
120 
121  // min/max options
122  group = new QActionGroup( this );
123  group->setExclusive( false );
124  connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
125  action = new QAction( tr( "Min/Max options" ), group );
126  action->setSeparator( true );
127  menu->addAction( action );
128  action = new QAction( tr( "Always show min/max markers" ), group );
129  action->setData( QVariant( "Show markers" ) );
130  action->setCheckable( true );
131  action->setChecked( mHistoShowMarkers );
132  menu->addAction( action );
133  action = new QAction( tr( "Zoom to min/max" ), group );
134  action->setData( QVariant( "Zoom min_max" ) );
135  action->setCheckable( true );
136  action->setChecked( mHistoZoomToMinMax );
137  menu->addAction( action );
138  action = new QAction( tr( "Update style to min/max" ), group );
139  action->setData( QVariant( "Update min_max" ) );
140  action->setCheckable( true );
141  action->setChecked( mHistoUpdateStyleToMinMax );
142  menu->addAction( action );
143 
144  // visibility options
145  group = new QActionGroup( this );
146  group->setExclusive( false );
147  connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
148  action = new QAction( tr( "Visibility" ), group );
149  action->setSeparator( true );
150  menu->addAction( action );
151  group = new QActionGroup( this );
152  group->setExclusive( true ); // these options are exclusive
153  connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
154  action = new QAction( tr( "Show all bands" ), group );
155  action->setData( QVariant( "Show all" ) );
156  action->setCheckable( true );
157  action->setChecked( mHistoShowBands == ShowAll );
158  menu->addAction( action );
159  action = new QAction( tr( "Show RGB/Gray band(s)" ), group );
160  action->setData( QVariant( "Show RGB" ) );
161  action->setCheckable( true );
162  action->setChecked( mHistoShowBands == ShowRGB );
163  menu->addAction( action );
164  action = new QAction( tr( "Show selected band" ), group );
165  action->setData( QVariant( "Show selected" ) );
166  action->setCheckable( true );
167  action->setChecked( mHistoShowBands == ShowSelected );
168  menu->addAction( action );
169 
170  // display options
171  group = new QActionGroup( this );
172  group->setExclusive( false );
173  connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
174  action = new QAction( tr( "Display" ), group );
175  action->setSeparator( true );
176  menu->addAction( action );
177  // should we plot as histogram instead of line plot? (int data only)
178  action = new QAction( "", group );
179  action->setData( QVariant( "Draw lines" ) );
180  if ( isInt )
181  {
182  action->setText( tr( "Draw as lines" ) );
183  action->setCheckable( true );
184  action->setChecked( mHistoDrawLines );
185  }
186  else
187  {
188  action->setText( tr( "Draw as lines (only int layers)" ) );
189  action->setEnabled( false );
190  }
191  menu->addAction( action );
192 
193  // actions
194  action = new QAction( tr( "Actions" ), group );
195  action->setSeparator( true );
196  menu->addAction( action );
197 
198  // load actions
199  group = new QActionGroup( this );
200  group->setExclusive( false );
201  connect( group, SIGNAL( triggered( QAction* ) ), this, SLOT( histoActionTriggered( QAction* ) ) );
202  action = new QAction( tr( "Reset" ), group );
203  action->setData( QVariant( "Load reset" ) );
204  menu->addAction( action );
205 
206  // these actions have been disabled for api cleanup, restore them eventually
207  // TODO restore these in qgis 2.4
208 #if 0
209  // Load min/max needs 3 params (method, extent, accuracy), cannot put it in single item
210  action = new QAction( tr( "Load min/max" ), group );
211  action->setSeparator( true );
212  menu->addAction( action );
213  action = new QAction( tr( "Estimate (faster)" ), group );
214  action->setData( QVariant( "Load estimate" ) );
215  menu->addAction( action );
216  action = new QAction( tr( "Actual (slower)" ), group );
217  action->setData( QVariant( "Load actual" ) );
218  menu->addAction( action );
219  action = new QAction( tr( "Current extent" ), group );
220  action->setData( QVariant( "Load extent" ) );
221  menu->addAction( action );
222  action = new QAction( tr( "Use stddev (1.0)" ), group );
223  action->setData( QVariant( "Load 1 stddev" ) );
224  menu->addAction( action );
225  action = new QAction( tr( "Use stddev (custom)" ), group );
226  action->setData( QVariant( "Load stddev" ) );
227  menu->addAction( action );
228  action = new QAction( tr( "Load for each band" ), group );
229  action->setData( QVariant( "Load apply all" ) );
230  action->setCheckable( true );
231  action->setChecked( mHistoLoadApplyAll );
232  menu->addAction( action );
233 #endif
234 
235  //others
236  action = new QAction( tr( "Recompute Histogram" ), group );
237  action->setData( QVariant( "Compute histogram" ) );
238  menu->addAction( action );
239 
240  }
241 
242 } // QgsRasterHistogramWidget ctor
243 
244 
246 {
247 }
248 
249 void QgsRasterHistogramWidget::setRendererWidget( const QString& name, QgsRasterRendererWidget* rendererWidget )
250 {
251  mRendererName = name;
252  mRendererWidget = rendererWidget;
254  on_cboHistoBand_currentIndexChanged( -1 );
255 }
256 
257 void QgsRasterHistogramWidget::setActive( bool theActiveFlag )
258 {
259  if ( theActiveFlag )
260  {
262  on_cboHistoBand_currentIndexChanged( -1 );
263  }
264  else
265  {
266  if ( QApplication::overrideCursor() )
267  QApplication::restoreOverrideCursor();
268  btnHistoMin->setChecked( false );
269  btnHistoMax->setChecked( false );
270  }
271 }
272 
273 void QgsRasterHistogramWidget::on_btnHistoCompute_clicked()
274 {
275 // Histogram computation can be called either by clicking the "Compute Histogram" button
276 // which is only visible if there is no cached histogram or by calling the
277 // "Compute Histogram" action. Due to limitations in the gdal api, it is not possible
278 // to re-calculate the histogram if it has already been calculated
279  computeHistogram( true );
281 }
282 
283 bool QgsRasterHistogramWidget::computeHistogram( bool forceComputeFlag )
284 {
285  QgsDebugMsg( "entered." );
286 
287  //bool myIgnoreOutOfRangeFlag = true;
288  //bool myThoroughBandScanFlag = false;
289  int myBandCountInt = mRasterLayer->bandCount();
290 
291  // if forceComputeFlag = false make sure raster has cached histogram, else return false
292  if ( ! forceComputeFlag )
293  {
294  for ( int myIteratorInt = 1;
295  myIteratorInt <= myBandCountInt;
296  ++myIteratorInt )
297  {
298  int sampleSize = 250000; // number of sample cells
299  if ( !mRasterLayer->dataProvider()->hasHistogram( myIteratorInt, 0, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize ) )
300  {
301  QgsDebugMsg( QString( "band %1 does not have cached histo" ).arg( myIteratorInt ) );
302  return false;
303  }
304  }
305  }
306 
307  // compute histogram
308  stackedWidget2->setCurrentIndex( 1 );
309  connect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
310  QApplication::setOverrideCursor( Qt::WaitCursor );
311 
312  for ( int myIteratorInt = 1;
313  myIteratorInt <= myBandCountInt;
314  ++myIteratorInt )
315  {
316  int sampleSize = 250000; // number of sample cells
317  mRasterLayer->dataProvider()->histogram( myIteratorInt, 0, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize );
318  }
319 
320  disconnect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
321  // mHistogramProgress->hide();
322  stackedWidget2->setCurrentIndex( 0 );
323  QApplication::restoreOverrideCursor();
324 
325  return true;
326 }
327 
328 
330 {
331  // Explanation:
332  // We use the gdal histogram creation routine is called for each selected
333  // layer. Currently the hist is hardcoded to create 256 bins. Each bin stores
334  // the total number of cells that fit into the range defined by that bin.
335  //
336  // The graph routine below determines the greatest number of pixels in any given
337  // bin in all selected layers, and the min. It then draws a scaled line between min
338  // and max - scaled to image height. 1 line drawn per selected band
339  //
340  int myBandCountInt = mRasterLayer->bandCount();
341 
342  QgsDebugMsg( "entered." );
343 
344  if ( ! computeHistogram( false ) )
345  {
346  QgsDebugMsg( QString( "raster does not have cached histogram" ) );
347  stackedWidget2->setCurrentIndex( 2 );
348  return;
349  }
350 
351  // clear plot
352  mpPlot->detachItems();
353 
354  //ensure all children get removed
355  mpPlot->setAutoDelete( true );
356  mpPlot->setTitle( QObject::tr( "Raster Histogram" ) );
357  mpPlot->insertLegend( new QwtLegend(), QwtPlot::BottomLegend );
358  // Set axis titles
359  mpPlot->setAxisTitle( QwtPlot::xBottom, QObject::tr( "Pixel Value" ) );
360  mpPlot->setAxisTitle( QwtPlot::yLeft, QObject::tr( "Frequency" ) );
361  mpPlot->setAxisAutoScale( QwtPlot::yLeft );
362 
363  // x axis scale only set after computing global min/max across bands (see below)
364  // add a grid
365  QwtPlotGrid * myGrid = new QwtPlotGrid();
366  myGrid->attach( mpPlot );
367 
368  // make colors list
369  mHistoColors.clear();
370  mHistoColors << Qt::black; // first element, not used
371  QVector<QColor> myColors;
372  myColors << Qt::red << Qt::green << Qt::blue << Qt::magenta << Qt::darkYellow << Qt::cyan;
373  qsrand( myBandCountInt * 100 ); // make sure colors are always the same for a given band count
374  while ( myColors.size() <= myBandCountInt )
375  {
376  myColors <<
377  QColor( 1 + ( int )( 255.0 * qrand() / ( RAND_MAX + 1.0 ) ),
378  1 + ( int )( 255.0 * qrand() / ( RAND_MAX + 1.0 ) ),
379  1 + ( int )( 255.0 * qrand() / ( RAND_MAX + 1.0 ) ) );
380  }
381  //randomise seed again
382  qsrand( time( NULL ) );
383 
384  // assign colors to each band, depending on the current RGB/gray band selection
385  // grayscale
386  QList< int > mySelectedBands = rendererSelectedBands();
387  if ( mRendererName == "singlebandgray" )
388  {
389  int myGrayBand = mySelectedBands[0];
390  for ( int i = 1; i <= myBandCountInt; i++ )
391  {
392  if ( i == myGrayBand )
393  {
394  mHistoColors << Qt::darkGray;
395  cboHistoBand->setItemData( i - 1, QColor( Qt::darkGray ), Qt::ForegroundRole );
396  }
397  else
398  {
399  if ( ! myColors.isEmpty() )
400  {
401  mHistoColors << myColors.first();
402  myColors.pop_front();
403  }
404  else
405  {
406  mHistoColors << Qt::black;
407  }
408  cboHistoBand->setItemData( i - 1, QColor( Qt::black ), Qt::ForegroundRole );
409  }
410  }
411  }
412  // RGB
413  else if ( mRendererName == "multibandcolor" )
414  {
415  int myRedBand = mySelectedBands[0];
416  int myGreenBand = mySelectedBands[1];
417  int myBlueBand = mySelectedBands[2];
418  // remove RGB, which are reserved for the actual RGB bands
419  // show name of RGB bands in appropriate color in bold
420  myColors.remove( 0, 3 );
421  for ( int i = 1; i <= myBandCountInt; i++ )
422  {
423  QColor myColor;
424  if ( i == myRedBand )
425  myColor = Qt::red;
426  else if ( i == myGreenBand )
427  myColor = Qt::green;
428  else if ( i == myBlueBand )
429  myColor = Qt::blue;
430  else
431  {
432  if ( ! myColors.isEmpty() )
433  {
434  myColor = myColors.first();
435  myColors.pop_front();
436  }
437  else
438  {
439  myColor = Qt::black;
440  }
441  cboHistoBand->setItemData( i - 1, QColor( Qt::black ), Qt::ForegroundRole );
442  }
443  if ( i == myRedBand || i == myGreenBand || i == myBlueBand )
444  {
445  cboHistoBand->setItemData( i - 1, myColor, Qt::ForegroundRole );
446  }
447  mHistoColors << myColor;
448  }
449  }
450  else
451  {
452  mHistoColors << myColors;
453  }
454 
455  //
456  //now draw actual graphs
457  //
458 
459  //somtimes there are more bins than needed
460  //we find out the last one that actually has data in it
461  //so we can discard the rest and set the x-axis scales correctly
462  //
463  // scan through to get counts from layers' histograms
464  //
465  mHistoMin = 0;
466  mHistoMax = 0;
467  bool myFirstIteration = true;
468  /* get selected band list, if mHistoShowBands != ShowAll */
469  mySelectedBands = histoSelectedBands();
470  double myBinXStep = 1;
471  double myBinX = 0;
472 
473  for ( int myIteratorInt = 1;
474  myIteratorInt <= myBandCountInt;
475  ++myIteratorInt )
476  {
477  /* skip this band if mHistoShowBands != ShowAll and this band is not selected */
478  if ( mHistoShowBands != ShowAll )
479  {
480  if ( ! mySelectedBands.contains( myIteratorInt ) )
481  continue;
482  }
483 
484  int sampleSize = 250000; // number of sample cells
485  QgsRasterHistogram myHistogram = mRasterLayer->dataProvider()->histogram( myIteratorInt, 0, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize );
486 
487  QgsDebugMsg( QString( "got raster histo for band %1 : min=%2 max=%3 count=%4" ).arg( myIteratorInt ).arg( myHistogram.minimum ).arg( myHistogram.maximum ).arg( myHistogram.binCount ) );
488 
489  QGis::DataType mySrcDataType = mRasterLayer->dataProvider()->srcDataType( myIteratorInt );
490  bool myDrawLines = true;
491  if ( ! mHistoDrawLines &&
492  ( mySrcDataType == QGis::Byte ||
493  mySrcDataType == QGis::Int16 || mySrcDataType == QGis::Int32 ||
494  mySrcDataType == QGis::UInt16 || mySrcDataType == QGis::UInt32 ) )
495  {
496  myDrawLines = false;
497  }
498 
499  QwtPlotCurve * mypCurve = 0;
500  if ( myDrawLines )
501  {
502  mypCurve = new QwtPlotCurve( tr( "Band %1" ).arg( myIteratorInt ) );
503  //mypCurve->setCurveAttribute( QwtPlotCurve::Fitted );
504  mypCurve->setRenderHint( QwtPlotItem::RenderAntialiased );
505  mypCurve->setPen( QPen( mHistoColors.at( myIteratorInt ) ) );
506  }
507 
508 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
509  QwtPlotHistogram * mypHisto = 0;
510  if ( ! myDrawLines )
511  {
512  mypHisto = new QwtPlotHistogram( tr( "Band %1" ).arg( myIteratorInt ) );
513  mypHisto->setRenderHint( QwtPlotItem::RenderAntialiased );
514  //mypHisto->setPen( QPen( mHistoColors.at( myIteratorInt ) ) );
515  mypHisto->setPen( QPen( Qt::lightGray ) );
516  // this is needed in order to see the colors in the legend
517  mypHisto->setBrush( QBrush( mHistoColors.at( myIteratorInt ) ) );
518  }
519 #else
520  HistogramItem *mypHistoItem = 0;
521  if ( ! myDrawLines )
522  {
523  mypHistoItem = new HistogramItem( tr( "Band %1" ).arg( myIteratorInt ) );
524  mypHistoItem->setRenderHint( QwtPlotItem::RenderAntialiased );
525  mypHistoItem->setColor( mHistoColors.at( myIteratorInt ) );
526  }
527 #endif
528 
529 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
530  QVector<QPointF> data;
531  QVector<QwtIntervalSample> dataHisto;
532 #else
533  QVector<double> myX2Data;
534  QVector<double> myY2Data;
535  // we safely assume that QT>=4.0 (min version is 4.7), therefore QwtArray is a QVector, so don't set size here
536  QwtArray<QwtDoubleInterval> intervalsHisto;
537  QwtArray<double> valuesHisto;
538 
539 #endif
540 
541  // calculate first bin x value and bin step size if not Byte data
542  if ( mySrcDataType != QGis::Byte )
543  {
544  myBinXStep = ( myHistogram.maximum - myHistogram.minimum ) / myHistogram.binCount;
545  myBinX = myHistogram.minimum + myBinXStep / 2.0;
546  }
547  else
548  {
549  myBinXStep = 1;
550  myBinX = 0;
551  }
552 
553  for ( int myBin = 0; myBin < myHistogram.binCount; myBin++ )
554  {
555  int myBinValue = myHistogram.histogramVector.at( myBin );
556 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
557  if ( myDrawLines )
558  {
559  data << QPointF( myBinX, myBinValue );
560  }
561  else
562  {
563  dataHisto << QwtIntervalSample( myBinValue, myBinX - myBinXStep / 2.0, myBinX + myBinXStep / 2.0 );
564  }
565 #else
566  if ( myDrawLines )
567  {
568  myX2Data.append( double( myBinX ) );
569  myY2Data.append( double( myBinValue ) );
570  }
571  else
572  {
573  intervalsHisto.append( QwtDoubleInterval( myBinX - myBinXStep / 2.0, myBinX + myBinXStep / 2.0 ) );
574  valuesHisto.append( double( myBinValue ) );
575  }
576 #endif
577  myBinX += myBinXStep;
578  }
579 
580 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
581  if ( myDrawLines )
582  {
583  mypCurve->setSamples( data );
584  mypCurve->attach( mpPlot );
585  }
586  else
587  {
588  mypHisto->setSamples( dataHisto );
589  mypHisto->attach( mpPlot );
590  }
591 #else
592  if ( myDrawLines )
593  {
594  mypCurve->setData( myX2Data, myY2Data );
595  mypCurve->attach( mpPlot );
596  }
597  else
598  {
599  mypHistoItem->setData( QwtIntervalData( intervalsHisto, valuesHisto ) );
600  mypHistoItem->attach( mpPlot );
601  }
602 #endif
603 
604  if ( myFirstIteration || mHistoMin > myHistogram.minimum )
605  {
606  mHistoMin = myHistogram.minimum;
607  }
608  if ( myFirstIteration || mHistoMax < myHistogram.maximum )
609  {
610  mHistoMax = myHistogram.maximum;
611  }
612  QgsDebugMsg( QString( "computed histo min = %1 max = %2" ).arg( mHistoMin ).arg( mHistoMax ) );
613  myFirstIteration = false;
614  }
615 
616  if ( mHistoMin < mHistoMax )
617  {
618  // for x axis use band pixel values rather than gdal hist. bin values
619  // subtract -0.5 to prevent rounding errors
620  // see http://www.gdal.org/classGDALRasterBand.html#3f8889607d3b2294f7e0f11181c201c8
621  // fix x range for non-Byte data
622  mpPlot->setAxisScale( QwtPlot::xBottom,
623  mHistoMin - myBinXStep / 2,
624  mHistoMax + myBinXStep / 2 );
625  mpPlot->setEnabled( true );
626  mpPlot->replot();
627 
628  // histo plot markers
629  // memory leak?
630  mHistoMarkerMin = new QwtPlotMarker();
631  mHistoMarkerMin->attach( mpPlot );
632  mHistoMarkerMax = new QwtPlotMarker();
633  mHistoMarkerMax->attach( mpPlot );
634  updateHistoMarkers();
635 
636  // histo picker
637  if ( !mHistoPicker )
638  {
639  mHistoPicker = new QwtPlotPicker( mpPlot->canvas() );
640  // mHistoPicker->setTrackerMode( QwtPicker::ActiveOnly );
641  mHistoPicker->setTrackerMode( QwtPicker::AlwaysOff );
642  mHistoPicker->setRubberBand( QwtPicker::VLineRubberBand );
643 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
644  mHistoPicker->setStateMachine( new QwtPickerDragPointMachine );
645  connect( mHistoPicker, SIGNAL( selected( const QPointF & ) ), this, SLOT( histoPickerSelected( const QPointF & ) ) );
646 #else
647  mHistoPicker->setSelectionFlags( QwtPicker::PointSelection | QwtPicker::DragSelection );
648  connect( mHistoPicker, SIGNAL( selected( const QwtDoublePoint & ) ), this, SLOT( histoPickerSelectedQwt5( const QwtDoublePoint & ) ) );
649 #endif
650  }
651  mHistoPicker->setEnabled( false );
652 
653  // plot zoomer
654  if ( !mHistoZoomer )
655  {
656  mHistoZoomer = new QwtPlotZoomer( mpPlot->canvas() );
657 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
658  mHistoZoomer->setStateMachine( new QwtPickerDragRectMachine );
659 #else
660  mHistoZoomer->setSelectionFlags( QwtPicker::RectSelection | QwtPicker::DragSelection );
661 #endif
662  mHistoZoomer->setTrackerMode( QwtPicker::AlwaysOff );
663  }
664  mHistoZoomer->setEnabled( true );
665  }
666  else
667  {
668  mpPlot->setDisabled( true );
669  if ( mHistoPicker )
670  mHistoPicker->setEnabled( false );
671  if ( mHistoZoomer )
672  mHistoZoomer->setEnabled( false );
673  }
674 
675  disconnect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
676  stackedWidget2->setCurrentIndex( 0 );
677  // icon from http://findicons.com/icon/169577/14_zoom?id=171427
678  mpPlot->canvas()->setCursor( QCursor( QgsApplication::getThemePixmap( "/mIconZoom.svg" ) ) );
679  // on_cboHistoBand_currentIndexChanged( -1 );
680  QApplication::restoreOverrideCursor();
681 }
682 
684 {
685  if ( mpPlot == 0 )
686  {
687  return;
688  }
689 
690  QPair< QString, QString> myFileNameAndFilter = QgisGui::getSaveAsImageName( this, tr( "Choose a file name to save the map image as" ) );
691  QFileInfo myInfo( myFileNameAndFilter.first );
692  if ( QFileInfo( myFileNameAndFilter.first ).baseName() != "" )
693  {
694  histoSaveAsImage( myFileNameAndFilter.first );
695  }
696 }
697 
698 bool QgsRasterHistogramWidget::histoSaveAsImage( const QString& theFilename,
699  int width, int height, int quality )
700 {
701  // make sure dir. exists
702  QFileInfo myInfo( theFilename );
703  QDir myDir( myInfo.dir() );
704  if ( ! myDir.exists() )
705  {
706  QgsDebugMsg( QString( "Error, directory %1 non-existent (theFilename = %2)" ).arg( myDir.absolutePath() ).arg( theFilename ) );
707  return false;
708  }
709 
710  // prepare the pixmap
711  QPixmap myPixmap( width, height );
712  QRect myQRect( 5, 5, width - 10, height - 10 ); // leave a 5px border on all sides
713  myPixmap.fill( Qt::white ); // Qt::transparent ?
714 
715 #if defined(QWT_VERSION) && QWT_VERSION>=0x060000
716  QwtPlotRenderer myRenderer;
717  myRenderer.setDiscardFlags( QwtPlotRenderer::DiscardBackground |
718  QwtPlotRenderer::DiscardCanvasBackground );
719  myRenderer.setLayoutFlags( QwtPlotRenderer::FrameWithScales );
720 
721  QPainter myPainter;
722  myPainter.begin( &myPixmap );
723  myRenderer.render( mpPlot, &myPainter, myQRect );
724  myPainter.end();
725 #else
726  QwtPlotPrintFilter myFilter;
727  int myOptions = QwtPlotPrintFilter::PrintAll;
728  myOptions &= ~QwtPlotPrintFilter::PrintBackground;
729  myOptions |= QwtPlotPrintFilter::PrintFrameWithScales;
730  myFilter.setOptions( myOptions );
731 
732  QPainter myPainter;
733  myPainter.begin( &myPixmap );
734  mpPlot->print( &myPainter, myQRect, myFilter );
735  myPainter.end();
736 
737  // "fix" for bug in qwt5 - legend and plot shifts a bit
738  // can't see how to avoid this without picking qwt5 apart...
741 #endif
742 
743  // save pixmap to file
744  myPixmap.save( theFilename, 0, quality );
745 
746  // should do more error checking
747  return true;
748 }
749 
751 {
752  cboHistoBand->setCurrentIndex( theBandNo - 1 );
753 }
754 
755 void QgsRasterHistogramWidget::on_cboHistoBand_currentIndexChanged( int index )
756 {
757  if ( mHistoShowBands == ShowSelected )
759 
760  // get the current index value, index can be -1
761  index = cboHistoBand->currentIndex();
762  if ( mHistoPicker != NULL )
763  {
764  mHistoPicker->setEnabled( false );
765  mHistoPicker->setRubberBandPen( QPen( mHistoColors.at( index + 1 ) ) );
766  }
767  if ( mHistoZoomer != NULL )
768  mHistoZoomer->setEnabled( true );
769  btnHistoMin->setEnabled( true );
770  btnHistoMax->setEnabled( true );
771 
772  QPair< QString, QString > myMinMax = rendererMinMax( index + 1 );
773  leHistoMin->setText( myMinMax.first );
774  leHistoMax->setText( myMinMax.second );
775 
776  applyHistoMin();
777  applyHistoMax();
778 }
779 
780 void QgsRasterHistogramWidget::histoActionTriggered( QAction* action )
781 {
782  if ( ! action )
783  return;
784  histoAction( action->data().toString(), action->isChecked() );
785 }
786 
787 void QgsRasterHistogramWidget::histoAction( const QString &actionName, bool actionFlag )
788 {
789  if ( actionName == "" )
790  return;
791 
792  // this approach is a bit of a hack, but this way we don't have to define slots for each action
793  QgsDebugMsg( QString( "band = %1 action = %2" ).arg( cboHistoBand->currentIndex() + 1 ).arg( actionName ) );
794 
795  // checkeable actions
796  if ( actionName == "Show markers" )
797  {
798  mHistoShowMarkers = actionFlag;
799  QSettings settings;
800  settings.setValue( "/Raster/histogram/showMarkers", mHistoShowMarkers );
801  updateHistoMarkers();
802  return;
803  }
804  else if ( actionName == "Zoom min_max" )
805  {
806  mHistoZoomToMinMax = actionFlag;
807  QSettings settings;
808  settings.setValue( "/Raster/histogram/zoomToMinMax", mHistoZoomToMinMax );
809  return;
810  }
811  else if ( actionName == "Update min_max" )
812  {
813  mHistoUpdateStyleToMinMax = actionFlag;
814  QSettings settings;
815  settings.setValue( "/Raster/histogram/updateStyleToMinMax", mHistoUpdateStyleToMinMax );
816  return;
817  }
818  else if ( actionName == "Show all" )
819  {
820  mHistoShowBands = ShowAll;
821  // settings.setValue( "/Raster/histogram/showBands", (int)mHistoShowBands );
823  return;
824  }
825  else if ( actionName == "Show selected" )
826  {
827  mHistoShowBands = ShowSelected;
828  // settings.setValue( "/Raster/histogram/showBands", (int)mHistoShowBands );
830  return;
831  }
832  else if ( actionName == "Show RGB" )
833  {
834  mHistoShowBands = ShowRGB;
835  // settings.setValue( "/Raster/histogram/showBands", (int)mHistoShowBands );
837  return;
838  }
839  else if ( actionName == "Draw lines" )
840  {
841  mHistoDrawLines = actionFlag;
842  QSettings settings;
843  settings.setValue( "/Raster/histogram/drawLines", mHistoDrawLines );
844  on_btnHistoCompute_clicked(); // refresh
845  return;
846  }
847 #if 0
848  else if ( actionName == "Load apply all" )
849  {
850  mHistoLoadApplyAll = actionFlag;
851  settings.setValue( "/Raster/histogram/loadApplyAll", mHistoLoadApplyAll );
852  return;
853  }
854 #endif
855  // Load actions
856  // TODO - separate calculations from rendererwidget so we can do them without
857  else if ( actionName.left( 5 ) == "Load " && mRendererWidget )
858  {
859  QVector<int> myBands;
860  bool ok = false;
861 
862 #if 0
863  double minMaxValues[2];
864 
865  // find which band(s) need updating (all or current)
866  if ( mHistoLoadApplyAll )
867  {
868  int myBandCountInt = mRasterLayer->bandCount();
869  for ( int i = 1; i <= myBandCountInt; i++ )
870  {
871  if ( i != cboHistoBand->currentIndex() + 1 )
872  myBands << i;
873  }
874  }
875 #endif
876 
877  // add current band to the end
878  myBands << cboHistoBand->currentIndex() + 1;
879 
880  // get stddev value once if needed
881  /*
882  double myStdDev = 1.0;
883  if ( actionName == "Load stddev" )
884  {
885  myStdDev = mRendererWidget->stdDev().toDouble();
886  }
887  */
888 
889  // don't update markers every time
890  leHistoMin->blockSignals( true );
891  leHistoMax->blockSignals( true );
892 
893  // process each band
894  foreach ( int theBandNo, myBands )
895  {
896  ok = false;
897 #if 0
898  if ( actionName == "Load actual" )
899  {
900  ok = mRendererWidget->bandMinMax( QgsRasterRendererWidget::Actual,
901  theBandNo, minMaxValues );
902  }
903  else if ( actionName == "Load estimate" )
904  {
905  ok = mRendererWidget->bandMinMax( QgsRasterRendererWidget::Estimate,
906  theBandNo, minMaxValues );
907  }
908  else if ( actionName == "Load extent" )
909  {
910  ok = mRendererWidget->bandMinMax( QgsRasterRendererWidget::CurrentExtent,
911  theBandNo, minMaxValues );
912  }
913  else if ( actionName == "Load 1 stddev" ||
914  actionName == "Load stddev" )
915  {
916  ok = mRendererWidget->bandMinMaxFromStdDev( myStdDev, theBandNo, minMaxValues );
917  }
918 #endif
919 
920  // apply current item
921  cboHistoBand->setCurrentIndex( theBandNo - 1 );
922  if ( !ok || actionName == "Load reset" )
923  {
924  leHistoMin->clear();
925  leHistoMax->clear();
926 #if 0
927  // TODO - fix gdal provider: changes data type when nodata value is not found
928  // this prevents us from getting proper min and max values here
930  ( QGis::DataType ) mRasterLayer->dataProvider()->dataType( theBandNo ) );
932  ( QGis::DataType ) mRasterLayer->dataProvider()->dataType( theBandNo ) );
933  }
934  else
935  {
936  leHistoMin->setText( QString::number( minMaxValues[0] ) );
937  leHistoMax->setText( QString::number( minMaxValues[1] ) );
938 #endif
939  }
940  applyHistoMin();
941  applyHistoMax();
942  }
943  // update markers
944  leHistoMin->blockSignals( false );
945  leHistoMax->blockSignals( false );
946  updateHistoMarkers();
947  }
948  else if ( actionName == "Compute histogram" )
949  {
950  on_btnHistoCompute_clicked();
951  }
952  else
953  {
954  QgsDebugMsg( "Invalid action " + actionName );
955  return;
956  }
957 }
958 
959 void QgsRasterHistogramWidget::applyHistoMin()
960 {
961  if ( ! mRendererWidget )
962  return;
963 
964  int theBandNo = cboHistoBand->currentIndex() + 1;
965  QList< int > mySelectedBands = rendererSelectedBands();
966  QString min;
967  for ( int i = 0; i <= mySelectedBands.size(); i++ )
968  {
969  if ( theBandNo == mRendererWidget->selectedBand( i ) )
970  {
971  min = leHistoMin->text();
972  if ( mHistoUpdateStyleToMinMax )
973  mRendererWidget->setMin( min, i );
974  }
975  }
976 
977  updateHistoMarkers();
978 
979  if ( ! min.isEmpty() && mHistoZoomToMinMax && mHistoZoomer )
980  {
981  QRectF rect = mHistoZoomer->zoomRect();
982  rect.setLeft( min.toDouble() );
983  mHistoZoomer->zoom( rect );
984  }
985 
986 }
987 
988 void QgsRasterHistogramWidget::applyHistoMax()
989 {
990  if ( ! mRendererWidget )
991  return;
992 
993  int theBandNo = cboHistoBand->currentIndex() + 1;
994  QList< int > mySelectedBands = rendererSelectedBands();
995  QString max;
996  for ( int i = 0; i <= mySelectedBands.size(); i++ )
997  {
998  if ( theBandNo == mRendererWidget->selectedBand( i ) )
999  {
1000  max = leHistoMax->text();
1001  if ( mHistoUpdateStyleToMinMax )
1002  mRendererWidget->setMax( max, i );
1003  }
1004  }
1005 
1006  updateHistoMarkers();
1007 
1008  if ( ! max.isEmpty() && mHistoZoomToMinMax && mHistoZoomer )
1009  {
1010  QRectF rect = mHistoZoomer->zoomRect();
1011  rect.setRight( max.toDouble() );
1012  mHistoZoomer->zoom( rect );
1013  }
1014 }
1015 
1016 void QgsRasterHistogramWidget::on_btnHistoMin_toggled()
1017 {
1018  if ( mpPlot != NULL && mHistoPicker != NULL )
1019  {
1020  if ( QApplication::overrideCursor() )
1021  QApplication::restoreOverrideCursor();
1022  if ( btnHistoMin->isChecked() )
1023  {
1024  btnHistoMax->setChecked( false );
1025  QApplication::setOverrideCursor( Qt::PointingHandCursor );
1026  }
1027  if ( mHistoZoomer != NULL )
1028  mHistoZoomer->setEnabled( ! btnHistoMin->isChecked() );
1029  mHistoPicker->setEnabled( btnHistoMin->isChecked() );
1030  }
1031  updateHistoMarkers();
1032 }
1033 
1034 void QgsRasterHistogramWidget::on_btnHistoMax_toggled()
1035 {
1036  if ( mpPlot != NULL && mHistoPicker != NULL )
1037  {
1038  if ( QApplication::overrideCursor() )
1039  QApplication::restoreOverrideCursor();
1040  if ( btnHistoMax->isChecked() )
1041  {
1042  btnHistoMin->setChecked( false );
1043  QApplication::setOverrideCursor( Qt::PointingHandCursor );
1044  }
1045  if ( mHistoZoomer != NULL )
1046  mHistoZoomer->setEnabled( ! btnHistoMax->isChecked() );
1047  mHistoPicker->setEnabled( btnHistoMax->isChecked() );
1048  }
1049  updateHistoMarkers();
1050 }
1051 
1052 // local function used by histoPickerSelected(), to get a rounded picked value
1053 // this is sensitive and may not always be correct, needs more testing
1054 QString findClosestTickVal( double target, const QwtScaleDiv * scale, int div = 100 )
1055 {
1056  if ( !scale ) return "";
1057 
1058  QList< double > minorTicks = scale->ticks( QwtScaleDiv::MinorTick );
1059  QList< double > majorTicks = scale->ticks( QwtScaleDiv::MajorTick );
1060  double diff = ( minorTicks[1] - minorTicks[0] ) / div;
1061  double min = majorTicks[0] - diff;
1062  if ( min > target )
1063  min -= ( majorTicks[1] - majorTicks[0] );
1064 #if defined(QWT_VERSION) && QWT_VERSION<0x050200
1065  double max = scale->hBound();
1066 #else
1067  double max = scale->upperBound();
1068 #endif
1069  double closest = target;
1070  double current = min;
1071 
1072  while ( current < max )
1073  {
1074  current += diff;
1075  if ( current > target )
1076  {
1077  closest = ( qAbs( target - current + diff ) < qAbs( target - current ) ) ? current - diff : current;
1078  break;
1079  }
1080  }
1081 
1082  // QgsDebugMsg( QString( "target=%1 div=%2 closest=%3" ).arg( target ).arg( div ).arg( closest ) );
1083  return QString::number( closest );
1084 }
1085 
1086 void QgsRasterHistogramWidget::histoPickerSelected( const QPointF & pos )
1087 {
1088  if ( btnHistoMin->isChecked() || btnHistoMax->isChecked() )
1089  {
1090 #if defined(QWT_VERSION) && QWT_VERSION>=0x060100
1091  const QwtScaleDiv * scale = &mpPlot->axisScaleDiv( QwtPlot::xBottom );
1092 #else
1093  const QwtScaleDiv * scale = mpPlot->axisScaleDiv( QwtPlot::xBottom );
1094 #endif
1095 
1096  if ( btnHistoMin->isChecked() )
1097  {
1098  leHistoMin->setText( findClosestTickVal( pos.x(), scale ) );
1099  applyHistoMin();
1100  btnHistoMin->setChecked( false );
1101  }
1102  else // if ( btnHistoMax->isChecked() )
1103  {
1104  leHistoMax->setText( findClosestTickVal( pos.x(), scale ) );
1105  applyHistoMax();
1106  btnHistoMax->setChecked( false );
1107  }
1108  }
1109  if ( QApplication::overrideCursor() )
1110  QApplication::restoreOverrideCursor();
1111 }
1112 
1113 void QgsRasterHistogramWidget::histoPickerSelectedQwt5( const QwtDoublePoint & pos )
1114 {
1115  histoPickerSelected( QPointF( pos.x(), pos.y() ) );
1116 }
1117 
1118 void QgsRasterHistogramWidget::updateHistoMarkers()
1119 {
1120  // hack to not update markers
1121  if ( leHistoMin->signalsBlocked() )
1122  return;
1123  // todo error checking
1124  if ( mpPlot == NULL || mHistoMarkerMin == NULL || mHistoMarkerMax == NULL )
1125  return;
1126 
1127  int theBandNo = cboHistoBand->currentIndex() + 1;
1128  QList< int > mySelectedBands = histoSelectedBands();
1129 
1130  if (( ! mHistoShowMarkers && ! btnHistoMin->isChecked() && ! btnHistoMax->isChecked() ) ||
1131  ( ! mySelectedBands.isEmpty() && ! mySelectedBands.contains( theBandNo ) ) )
1132  {
1133  mHistoMarkerMin->hide();
1134  mHistoMarkerMax->hide();
1135  mpPlot->replot();
1136  return;
1137  }
1138 
1139  double minVal = mHistoMin;
1140  double maxVal = mHistoMax;
1141  QString minStr = leHistoMin->text();
1142  QString maxStr = leHistoMax->text();
1143  if ( minStr != "" )
1144  minVal = minStr.toDouble();
1145  if ( maxStr != "" )
1146  maxVal = maxStr.toDouble();
1147 
1148  QPen linePen = QPen( mHistoColors.at( theBandNo ) );
1149  linePen.setStyle( Qt::DashLine );
1150  mHistoMarkerMin->setLineStyle( QwtPlotMarker::VLine );
1151  mHistoMarkerMin->setLinePen( linePen );
1152  mHistoMarkerMin->setXValue( minVal );
1153  mHistoMarkerMin->show();
1154  mHistoMarkerMax->setLineStyle( QwtPlotMarker::VLine );
1155  mHistoMarkerMax->setLinePen( linePen );
1156  mHistoMarkerMax->setXValue( maxVal );
1157  mHistoMarkerMax->show();
1158 
1159  mpPlot->replot();
1160 }
1161 
1162 
1163 QList< int > QgsRasterHistogramWidget::histoSelectedBands()
1164 {
1165  QList< int > mySelectedBands;
1166 
1167  if ( mHistoShowBands != ShowAll )
1168  {
1169  if ( mHistoShowBands == ShowSelected )
1170  {
1171  mySelectedBands << cboHistoBand->currentIndex() + 1;
1172  }
1173  else if ( mHistoShowBands == ShowRGB )
1174  {
1175  mySelectedBands = rendererSelectedBands();
1176  }
1177  }
1178 
1179  return mySelectedBands;
1180 }
1181 
1182 QList< int > QgsRasterHistogramWidget::rendererSelectedBands()
1183 {
1184  QList< int > mySelectedBands;
1185 
1186  if ( ! mRendererWidget )
1187  {
1188  mySelectedBands << -1 << -1 << -1; // make sure we return 3 elements
1189  return mySelectedBands;
1190  }
1191 
1192  if ( mRendererName == "singlebandgray" )
1193  {
1194  mySelectedBands << mRendererWidget->selectedBand();
1195  }
1196  else if ( mRendererName == "multibandcolor" )
1197  {
1198  for ( int i = 0; i <= 2; i++ )
1199  {
1200  mySelectedBands << mRendererWidget->selectedBand( i );
1201  }
1202  }
1203 
1204  return mySelectedBands;
1205 }
1206 
1207 QPair< QString, QString > QgsRasterHistogramWidget::rendererMinMax( int theBandNo )
1208 {
1209  QPair< QString, QString > myMinMax;
1210 
1211  if ( ! mRendererWidget )
1212  return myMinMax;
1213 
1214  if ( mRendererName == "singlebandgray" )
1215  {
1216  if ( theBandNo == mRendererWidget->selectedBand() )
1217  {
1218  myMinMax.first = mRendererWidget->min();
1219  myMinMax.second = mRendererWidget->max();
1220  }
1221  }
1222  else if ( mRendererName == "multibandcolor" )
1223  {
1224  for ( int i = 0; i <= 2; i++ )
1225  {
1226  if ( theBandNo == mRendererWidget->selectedBand( i ) )
1227  {
1228  myMinMax.first = mRendererWidget->min( i );
1229  myMinMax.second = mRendererWidget->max( i );
1230  break;
1231  }
1232  }
1233  }
1234 
1235  // TODO - there are 2 definitions of raster data type that should be unified
1236  // QgsRasterDataProvider::DataType and QGis::DataType
1237  // TODO - fix gdal provider: changes data type when nodata value is not found
1238  // this prevents us from getting proper min and max values here
1239  // minStr = QString::number( QgsContrastEnhancement::minimumValuePossible( ( QGis::DataType )
1240  // mRasterLayer->dataProvider()->dataType( theBandNo ) ) );
1241  // maxStr = QString::number( QgsContrastEnhancement::maximumValuePossible( ( QGis::DataType )
1242  // mRasterLayer->dataProvider()->dataType( theBandNo ) ) );
1243 
1244  // if we get an empty result, fill with default value (histo min/max)
1245  if ( myMinMax.first.isEmpty() )
1246  myMinMax.first = QString::number( mHistoMin );
1247  if ( myMinMax.second.isEmpty() )
1248  myMinMax.second = QString::number( mHistoMax );
1249 
1250  QgsDebugMsg( QString( "bandNo %1 got min/max [%2] [%3]" ).arg( theBandNo ).arg( myMinMax.first ).arg( myMinMax.second ) );
1251 
1252  return myMinMax;
1253 }