435 changes: 346 additions & 89 deletions src/core/qgsrasterdataprovider.cpp

Large diffs are not rendered by default.

48 changes: 43 additions & 5 deletions src/core/qgsrasterdataprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,20 +317,31 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast

/** \brief Get histogram. Histograms are cached in providers.
* @param theBandNo The band (number).
* @param theMinimum Minimum value.
* @param theMaximum Maximum value.
* @param theBinCount Number of bins (intervals,buckets). If 0, the number of bins is decided automaticaly according to data type, raster size etc.
* @param theMinimum Minimum value, if NaN, raster minimum value will be used.
* @param theMaximum Maximum value, if NaN, raster minimum value will be used.
* @param theExtent Extent used to calc histogram, if empty, whole raster extent is used.
* @param theSampleSize Approximate number of cells in sample. If 0, all cells (whole raster will be used). If raster does not have exact size (WCS without exact size for example), provider decides size of sample.
* @return Vector of non NULL cell counts for each bin.
*/
virtual QgsRasterHistogram histogram( int theBandNo,
double theMinimum, double theMaximum,
int theBinCount = 0,
double theMinimum = std::numeric_limits<double>::quiet_NaN(),
double theMaximum = std::numeric_limits<double>::quiet_NaN(),
const QgsRectangle & theExtent = QgsRectangle(),
int theSampleSize = 0,
bool theIncludeOutOfRange = false );

/** \brief Returns true if histogram is available (cached, already calculated), the parameters are the same as in histogram() */
virtual bool hasHistogram( int theBandNo,
int theBinCount = 0,
double theMinimum = std::numeric_limits<double>::quiet_NaN(),
double theMaximum = std::numeric_limits<double>::quiet_NaN(),
const QgsRectangle & theExtent = QgsRectangle(),
int theSampleSize = 0,
bool theIncludeOutOfRange = false );


/** \brief Create pyramid overviews */
virtual QString buildPyramids( const QList<QgsRasterPyramid> & thePyramidList,
const QString & theResamplingMethod = "NEAREST",
Expand All @@ -347,7 +358,17 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast
/** If the provider supports it, return band stats for the
given band. Default behaviour is to blockwise read the data
and generate the stats unless the provider overloads this function. */
virtual QgsRasterBandStats bandStatistics( int theBandNo );
//virtual QgsRasterBandStats bandStatistics( int theBandNo );

/** \brief Get band statistics.
* @param theBandNo The band (number).
* @param theExtent Extent used to calc histogram, if empty, whole raster extent is used.
* @param theSampleSize Approximate number of cells in sample. If 0, all cells (whole raster will be used). If raster does not have exact size (WCS without exact size for example), provider decides size of sample.
* @return Band statistics.
*/
virtual QgsRasterBandStats bandStatistics( int theBandNo,
const QgsRectangle & theExtent = QgsRectangle(),
int theSampleSize = 0 );

/** \brief helper function to create zero padded band names */
QString generateBandName( int theBandNumber ) const
Expand Down Expand Up @@ -521,8 +542,25 @@ class CORE_EXPORT QgsRasterDataProvider : public QgsDataProvider, public QgsRast

QgsRectangle mExtent;

/** \brief List of cached statistics, all bands mixed */
QList <QgsRasterBandStats> mStatistics;

/** \brief List of cached histograms, all bands mixed */
QList <QgsRasterHistogram> mHistograms;
};

/** Fill in histogram defaults if not specified */
virtual QgsRasterHistogram histogramDefaults( int theBandNo,

This comment has been minimized.

Copy link
@etiennesky

etiennesky Jul 26, 2012

Contributor

not sure if this name is adequate (I could not understand its use without reading the code) - perhaps it would be better to call it something like defaultHistogram() or getEmptyHistogram() - same for statisticsDefautls()

or change the docs to read "return a new histogram definition, with default values if not specified"

This comment has been minimized.

Copy link
@blazek

blazek Jul 27, 2012

Author Member

I know, but defaultHistogram() could be even more confusing, it could mean to get histogram with calculated data. I would suggest
void initHistogram ( QgsRasterHistogram &histogram, int theBandNo, int theBinCount = 0, ......)

This comment has been minimized.

Copy link
@etiennesky

etiennesky Jul 27, 2012

Contributor

yes, with updated docs also

int theBinCount = 0,
double theMinimum = std::numeric_limits<double>::quiet_NaN(),
double theMaximum = std::numeric_limits<double>::quiet_NaN(),
const QgsRectangle & theExtent = QgsRectangle(),
int theSampleSize = 0,
bool theIncludeOutOfRange = false );

/** Fill in statistics defaults if not specified */
virtual QgsRasterBandStats statisticsDefaults( int theBandNo,
const QgsRectangle & theExtent = QgsRectangle(),
int theBinCount = 0 );

};
#endif
36 changes: 22 additions & 14 deletions src/core/raster/qgsrasterbandstats.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@
#include <limits>

#include "qgscolorrampshader.h"
#include "qgsrectangle.h"

/** \ingroup core
* The RasterBandStats struct is a container for statistics about a single
* raster band.
*/
class CORE_EXPORT QgsRasterBandStats
{
public:
typedef QVector<int> HistogramVector;

QgsRasterBandStats()
{
bandName = "";
Expand All @@ -45,9 +45,17 @@ class CORE_EXPORT QgsRasterBandStats
stdDev = 0.0;
sum = 0.0;
elementCount = 0;
histogramVector = new HistogramVector();
isHistogramEstimated = false;
isHistogramOutOfRange = false;
width = 0;
height = 0;
}

/*! Compares region, size etc. not collected statistics */
bool operator==( const QgsRasterBandStats &s ) const
{
return ( s.bandNumber == bandNumber &&
s.extent == extent &&
s.width == width &&
s.height == height );
}

/** \brief The name of the band that these stats belong to. */
Expand All @@ -63,15 +71,6 @@ class CORE_EXPORT QgsRasterBandStats
* TODO: check if NO_DATA are excluded!*/
int elementCount;

/** \brief whteher histogram values are estimated or completely calculated */
bool isHistogramEstimated;

/** whehter histogram compuation should include out of range values */
bool isHistogramOutOfRange;

/** \brief Store the histogram for a given layer */
HistogramVector * histogramVector;

/** \brief The maximum cell value in the raster band. NO_DATA values
* are ignored. This does not use the gdal GetMaximmum function. */
double maximumValue;
Expand All @@ -98,5 +97,14 @@ class CORE_EXPORT QgsRasterBandStats

/** \brief The sum of the squares. Used to calculate standard deviation. */
double sumOfSquares;

/** \brief Number of columns used to calc statistics */
int width;

/** \brief Number of rows used to calc statistics */
int height;

/** \brief Extent used to calc statistics */
QgsRectangle extent;
};
#endif
21 changes: 15 additions & 6 deletions src/core/raster/qgsrasterhistogram.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* The QgsRasterHistogram is a container for histogram of a single raster band.
* It is used to cache computed histograms in raster providers.
*/
class CORE_EXPORT QgsRasterHistogram
class CORE_EXPORT QgsRasterHistogram
{
public:
typedef QVector<int> HistogramVector;
Expand All @@ -37,7 +37,6 @@ class CORE_EXPORT QgsRasterHistogram
bandNumber = 0;
binCount = 0;
nonNullCount = 0;
//sampleSize = 0;
includeOutOfRange = false;
maximum = 0;
minimum = 0;
Expand All @@ -46,18 +45,28 @@ class CORE_EXPORT QgsRasterHistogram
valid = false;
}

/*! Compares region, size etc. not histogram itself */
bool operator==( const QgsRasterHistogram &h ) const
{
return ( h.bandNumber == bandNumber &&
h.binCount == binCount &&
h.includeOutOfRange == includeOutOfRange &&
h.maximum == maximum &&
h.minimum == minimum &&
h.extent == extent &&
h.width == width &&
h.height == height );
}

/** \brief The gdal band number (starts at 1)*/
int bandNumber;

/** \brief Number of bins (intervals,buckets) in histogram. */
int binCount;

/** \brief The number of non NULL cells used to calculate histogram. */
int nonNullCount;

/** \brief Approximate number of cells used to calc histogram. Approximately
* width * height. */
//int sampleSize;

/** \brief Whether histogram includes out of range values (in first and last bin) */
bool includeOutOfRange;

Expand Down
1 change: 0 additions & 1 deletion src/core/raster/qgsrasterlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1729,7 +1729,6 @@ void QgsRasterLayer::setDataProvider( QString const & provider )
myRasterBandStats.bandName = mDataProvider->generateBandName( i );
myRasterBandStats.bandNumber = i;
myRasterBandStats.statsGathered = false;
myRasterBandStats.histogramVector->clear();
//Store the default color table
//readColorTable( i, &myRasterBandStats.colorTable );
QList<QgsColorRampShader::ColorRampItem> ct;
Expand Down
14 changes: 11 additions & 3 deletions src/gui/raster/qgsrasterhistogramwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ bool QgsRasterHistogramWidget::computeHistogram( bool forceComputeFlag )
myIteratorInt <= myBandCountInt;
++myIteratorInt )
{
if ( ! mRasterLayer->hasCachedHistogram( myIteratorInt, BINCOUNT ) )
//if ( ! mRasterLayer->hasCachedHistogram( myIteratorInt, BINCOUNT ) )
int sampleSize = 250000; // number of sample cells
if ( !mRasterLayer->dataProvider()->hasHistogram( myIteratorInt, BINCOUNT, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize ) )

This comment has been minimized.

Copy link
@etiennesky

etiennesky Jul 26, 2012

Contributor

it might be better to let the gdal provider decide the default values for sampleSize and bin count?

This comment has been minimized.

{
QgsDebugMsg( QString( "band %1 does not have cached histo" ).arg( myIteratorInt ) );
return false;
Expand All @@ -232,7 +234,9 @@ bool QgsRasterHistogramWidget::computeHistogram( bool forceComputeFlag )
myIteratorInt <= myBandCountInt;
++myIteratorInt )
{
mRasterLayer->populateHistogram( myIteratorInt, BINCOUNT, myIgnoreOutOfRangeFlag, myThoroughBandScanFlag );
//mRasterLayer->populateHistogram( myIteratorInt, BINCOUNT, myIgnoreOutOfRangeFlag, myThoroughBandScanFlag );
int sampleSize = 250000; // number of sample cells
mRasterLayer->dataProvider()->histogram( myIteratorInt, BINCOUNT, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize );
}

disconnect( mRasterLayer, SIGNAL( progressUpdate( int ) ), mHistogramProgress, SLOT( setValue( int ) ) );
Expand Down Expand Up @@ -401,6 +405,9 @@ void QgsRasterHistogramWidget::refreshHistogram()
}
QgsRasterBandStats myRasterBandStats = mRasterLayer->bandStatistics( myIteratorInt );
// mRasterLayer->populateHistogram( myIteratorInt, BINCOUNT, myIgnoreOutOfRangeFlag, myThoroughBandScanFlag );
int sampleSize = 250000; // number of sample cells
QgsRasterHistogram myHistogram = mRasterLayer->dataProvider()->histogram( myIteratorInt, BINCOUNT, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize );

QwtPlotCurve * mypCurve = new QwtPlotCurve( tr( "Band %1" ).arg( myIteratorInt ) );
mypCurve->setCurveAttribute( QwtPlotCurve::Fitted );
mypCurve->setRenderHint( QwtPlotItem::RenderAntialiased );
Expand All @@ -425,7 +432,8 @@ void QgsRasterHistogramWidget::refreshHistogram()

for ( int myBin = 0; myBin < BINCOUNT; myBin++ )
{
int myBinValue = myRasterBandStats.histogramVector->at( myBin );
//int myBinValue = myRasterBandStats.histogramVector->at( myBin );
int myBinValue = myHistogram.histogramVector.at( myBin );
#if defined(QWT_VERSION) && QWT_VERSION>=0x060000
data << QPointF( myBinX, myBinValue );
#else
Expand Down
25 changes: 13 additions & 12 deletions src/gui/raster/qgsrasterrendererwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,29 +78,30 @@ bool QgsRasterRendererWidget::bandMinMax( LoadMinMaxAlgo loadAlgo, int band, dou
else if ( loadAlgo == CumulativeCut )
{
// Currently 2 - 98% cumulative pixel count cut
bool myIgnoreOutOfRangeFlag = true;
bool myThoroughBandScanFlag = false;
mRasterLayer->populateHistogram( band, RASTER_HISTOGRAM_BINS, myIgnoreOutOfRangeFlag, myThoroughBandScanFlag );
//QgsRasterBandStats myRasterBandStats = mRasterLayer->bandStatistics( band );

QgsRasterBandStats myRasterBandStats = mRasterLayer->bandStatistics( band );
double myBinXStep = myRasterBandStats.range / RASTER_HISTOGRAM_BINS;
int sampleSize = 250000; // number of sample cells
QgsRasterHistogram myHistogram = mRasterLayer->dataProvider()->histogram( band, 0, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::quiet_NaN(), QgsRectangle(), sampleSize );

double myBinXStep = ( myHistogram.maximum - myHistogram.minimum ) / myHistogram.binCount;
int myCount = 0;
int myMinCount = ( int ) qRound( 0.02 * myRasterBandStats.elementCount );
int myMaxCount = ( int ) qRound( 0.98 * myRasterBandStats.elementCount );
int myMinCount = ( int ) qRound( 0.02 * myHistogram.nonNullCount );
int myMaxCount = ( int ) qRound( 0.98 * myHistogram.nonNullCount );
bool myMinFound = false;
QgsDebugMsg( QString( "RASTER_HISTOGRAM_BINS = %1 range = %2 minimumValue = %3 myBinXStep = %4" ).arg( RASTER_HISTOGRAM_BINS ).arg( myRasterBandStats.range ).arg( myRasterBandStats.minimumValue ).arg( myBinXStep ) );
for ( int myBin = 0; myBin < RASTER_HISTOGRAM_BINS; myBin++ )
QgsDebugMsg( QString( "binCount = %1 minimum = %2 maximum = %3 myBinXStep = %4" ).arg( myHistogram.binCount ).arg( myHistogram.minimum ).arg( myHistogram.maximum ).arg( myBinXStep ) );

for ( int myBin = 0; myBin < myHistogram.histogramVector.size(); myBin++ )
{
int myBinValue = myRasterBandStats.histogramVector->value( myBin );
int myBinValue = myHistogram.histogramVector.value( myBin );
myCount += myBinValue;
if ( !myMinFound && myCount > myMinCount )
{
minMaxValues[0] = myRasterBandStats.minimumValue + myBin * myBinXStep;
minMaxValues[0] = myHistogram.minimum + myBin * myBinXStep;
myMinFound = true;
}
if ( myCount > myMaxCount )
{
minMaxValues[1] = myRasterBandStats.minimumValue + myBin * myBinXStep;
minMaxValues[1] = myHistogram.minimum + myBin * myBinXStep;
break;
}
}
Expand Down
278 changes: 200 additions & 78 deletions src/providers/gdal/qgsgdalprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1243,124 +1243,212 @@ QStringList QgsGdalProvider::subLayers( GDALDatasetH dataset )
return subLayers;
}

bool QgsGdalProvider::hasCachedHistogram( int theBandNo, int theBinCount )
bool QgsGdalProvider::hasHistogram( int theBandNo,
int theBinCount,
double theMinimum, double theMaximum,
const QgsRectangle & theExtent,
int theSampleSize,
bool theIncludeOutOfRange )
{
QgsDebugMsg( QString( "theBandNo = %1 theBinCount = %2 theMinimum = %3 theMaximum = %4 theSampleSize = %5" ).arg( theBandNo ).arg( theBinCount ).arg( theMinimum ).arg( theMaximum ).arg( theSampleSize ) );

// First check if cached in mHistograms
if ( QgsRasterDataProvider::hasHistogram( theBandNo, theBinCount, theMinimum, theMaximum, theExtent, theSampleSize, theIncludeOutOfRange ) )
{
return true;
}

QgsRasterHistogram myHistogram = histogramDefaults( theBandNo, theBinCount, theMinimum, theMaximum, theExtent, theSampleSize, theIncludeOutOfRange );

// If not cached, check if supported by GDAL
if ( myHistogram.extent != extent() )
{
QgsDebugMsg( "Not supported by GDAL." );
return false;
}

QgsDebugMsg( "Looking for GDAL histogram xxxx" );

GDALRasterBandH myGdalBand = GDALGetRasterBand( mGdalDataset, theBandNo );
if ( ! myGdalBand )
{
return false;
}

// get default histogram with force=false to see if there is a cached histo
double myMinVal, myMaxVal;
int myBinCount;
int *myHistogramArray = 0;

// TODO: GDALGetDefaultHistogram has no bIncludeOutOfRange and bApproxOK,
// consider consequences
CPLErr myError = GDALGetDefaultHistogram( myGdalBand, &myMinVal, &myMaxVal,
&myBinCount, &myHistogramArray, false,
NULL, NULL );

if ( myHistogramArray )
VSIFree( myHistogramArray );

// if there was any error/warning assume the histogram is not valid or non-existent
if ( myError != CE_None )
{
QgsDebugMsg( "Cannot get default GDAL histogram" );
return false;
}

// This is fragile
double myExpectedMinVal = myHistogram.minimum;
double myExpectedMaxVal = myHistogram.maximum;

// make sure the cached histo has the same number of bins than requested
if ( myBinCount != theBinCount )
double dfHalfBucket = ( myExpectedMaxVal - myExpectedMinVal ) / ( 2 * myHistogram.binCount );
myExpectedMinVal -= dfHalfBucket;
myExpectedMaxVal += dfHalfBucket;

// min/max are stored as text in aux file => use threshold
if ( myBinCount != myHistogram.binCount ||
qAbs( myMinVal - myExpectedMinVal ) > qAbs( myExpectedMinVal ) / 10e6 ||
qAbs( myMaxVal - myExpectedMaxVal ) > qAbs( myExpectedMaxVal ) / 10e6 )
{
QgsDebugMsg( QString( "Params do not match binCount: %1 x %2, minVal: %3 x %4, maxVal: %5 x %6" ).arg( myBinCount ).arg( myHistogram.binCount ).arg( myMinVal ).arg( myExpectedMinVal ).arg( myMaxVal ).arg( myExpectedMaxVal ) );
return false;
}

QgsDebugMsg( "GDAL has cached histogram" );

// This should be enough, possible call to histogram() should retrieve the histogram cached in GDAL

return true;
}

void QgsGdalProvider::populateHistogram( int theBandNo, QgsRasterBandStats & theBandStats, int theBinCount, bool theIgnoreOutOfRangeFlag, bool theHistogramEstimatedFlag )
QgsRasterHistogram QgsGdalProvider::histogram( int theBandNo,
int theBinCount,
double theMinimum, double theMaximum,
const QgsRectangle & theExtent,
int theSampleSize,
bool theIncludeOutOfRange )
{
QgsDebugMsg( QString( "theBandNo = %1 theBinCount = %2 theMinimum = %3 theMaximum = %4 theSampleSize = %5" ).arg( theBandNo ).arg( theBinCount ).arg( theMinimum ).arg( theMaximum ).arg( theSampleSize ) );

QgsRasterHistogram myHistogram = histogramDefaults( theBandNo, theBinCount, theMinimum, theMaximum, theExtent, theSampleSize, theIncludeOutOfRange );

// Find cached
foreach( QgsRasterHistogram histogram, mHistograms )
{
if ( histogram == myHistogram )
{
QgsDebugMsg( "Using cached histogram." );
return histogram;
}
}

if ( myHistogram.extent != extent() )
{
QgsDebugMsg( "Using generic histogram." );
return QgsRasterDataProvider::histogram( theBandNo, theBinCount, theMinimum, theMaximum, theExtent, theSampleSize, theIncludeOutOfRange );
}

QgsDebugMsg( "Computing GDAL histogram" );

GDALRasterBandH myGdalBand = GDALGetRasterBand( mGdalDataset, theBandNo );
//QgsRasterBandStats myRasterBandStats = bandStatistics( theBandNo );
//calculate the histogram for this band
//we assume that it only needs to be calculated if the length of the histogram
//vector is not equal to the number of bins
//i.e if the histogram has never previously been generated or the user has
//selected a new number of bins.
if ( theBandStats.histogramVector == 0 ||
theBandStats.histogramVector->size() != theBinCount ||
theIgnoreOutOfRangeFlag != theBandStats.isHistogramOutOfRange ||
theHistogramEstimatedFlag != theBandStats.isHistogramEstimated )
{
QgsDebugMsg( "Computing histogram" );
theBandStats.histogramVector->clear();
theBandStats.isHistogramEstimated = theHistogramEstimatedFlag;
theBandStats.isHistogramOutOfRange = theIgnoreOutOfRangeFlag;
int *myHistogramArray = new int[theBinCount];

QgsGdalProgress myProg;
myProg.type = ProgressHistogram;
myProg.provider = this;

int bApproxOK = false;
if ( theSampleSize > 0 )
{
if (( xSize() * ySize() / theSampleSize ) > 2 ) // not perfect
{
bApproxOK = true;
}
}

QgsGdalProgress myProg;
myProg.type = ProgressHistogram;
myProg.provider = this;

#if 0 // this is the old method

double myerval = ( theBandStats.maximumValue - theBandStats.minimumValue ) / theBinCount;
GDALGetRasterHistogram( myGdalBand, theBandStats.minimumValue - 0.1*myerval,
theBandStats.maximumValue + 0.1*myerval, theBinCount, myHistogramArray,
theIgnoreOutOfRangeFlag, theHistogramEstimatedFlag, progressCallback,
&myProg ); //this is the arg for our custom gdal progress callback
double myerval = ( theBandStats.maximumValue - theBandStats.minimumValue ) / theBinCount;
GDALGetRasterHistogram( myGdalBand, theBandStats.minimumValue - 0.1*myerval,
theBandStats.maximumValue + 0.1*myerval, theBinCount, myHistogramArray,
theIgnoreOutOfRangeFlag, theHistogramEstimatedFlag, progressCallback,
&myProg ); //this is the arg for our custom gdal progress callback

#else // this is the new method, which gets a "Default" histogram

// calculate min/max like in GDALRasterBand::GetDefaultHistogram, but don't call it directly
// because there is no bApproxOK argument - that is lacking from the API
double myMinVal, myMaxVal;
const char* pszPixelType = GDALGetMetadataItem( myGdalBand, "PIXELTYPE", "IMAGE_STRUCTURE" );
int bSignedByte = ( pszPixelType != NULL && EQUAL( pszPixelType, "SIGNEDBYTE" ) );
// calculate min/max like in GDALRasterBand::GetDefaultHistogram, but don't call it directly
// because there is no bApproxOK argument - that is lacking from the API

if ( GDALGetRasterDataType( myGdalBand ) == GDT_Byte && !bSignedByte )
{
myMinVal = -0.5;
myMaxVal = 255.5;
}
else
{
CPLErr eErr = CE_Failure;
double dfHalfBucket = 0;
eErr = GDALGetRasterStatistics( myGdalBand, TRUE, TRUE, &myMinVal, &myMaxVal, NULL, NULL );
if ( eErr != CE_None )
{
delete [] myHistogramArray;
return;
}
dfHalfBucket = ( myMaxVal - myMinVal ) / ( 2 * theBinCount );
myMinVal -= dfHalfBucket;
myMaxVal += dfHalfBucket;
}
// Min/max, if not specified, are set by histogramDefaults, it does not
// set however min/max shifted to avoid rounding errors

double myMinVal = myHistogram.minimum;
double myMaxVal = myHistogram.maximum;

double dfHalfBucket = ( myMaxVal - myMinVal ) / ( 2 * myHistogram.binCount );
myMinVal -= dfHalfBucket;
myMaxVal += dfHalfBucket;

/*
const char* pszPixelType = GDALGetMetadataItem( myGdalBand, "PIXELTYPE", "IMAGE_STRUCTURE" );
int bSignedByte = ( pszPixelType != NULL && EQUAL( pszPixelType, "SIGNEDBYTE" ) );
CPLErr myError = GDALGetRasterHistogram( myGdalBand, myMinVal, myMaxVal,
theBinCount, myHistogramArray,
theIgnoreOutOfRangeFlag, theHistogramEstimatedFlag, progressCallback,
&myProg ); //this is the arg for our custom gdal progress callback
if ( myError != CE_None )
if ( GDALGetRasterDataType( myGdalBand ) == GDT_Byte && !bSignedByte )

This comment has been minimized.

Copy link
@etiennesky

etiennesky Jul 26, 2012

Contributor

reverting this makes byte histograms ugly with holes in the middle of the histogram - it is better to keep min a 0, max as 255 with 256 bins, so you have a count for each possible value, like gdal does. Unless your changes fixes this? I can't look into this for now.

This comment has been minimized.

Copy link
@blazek

blazek Jul 27, 2012

Author Member

This comment has been minimized.

Copy link
@etiennesky

etiennesky Jul 27, 2012

Contributor

ok thanks, didn't see that part!

{
myMinVal = -0.5;
myMaxVal = 255.5;
}
else
{
CPLErr eErr = CE_Failure;
double dfHalfBucket = 0;
eErr = GDALGetRasterStatistics( myGdalBand, TRUE, TRUE, &myMinVal, &myMaxVal, NULL, NULL );
if ( eErr != CE_None )
{
delete [] myHistogramArray;
return;
}
dfHalfBucket = ( myMaxVal - myMinVal ) / ( 2 * theBinCount );
myMinVal -= dfHalfBucket;
myMaxVal += dfHalfBucket;
}
*/

int *myHistogramArray = new int[myHistogram.binCount];
CPLErr myError = GDALGetRasterHistogram( myGdalBand, myMinVal, myMaxVal,
myHistogram.binCount, myHistogramArray,
theIncludeOutOfRange, bApproxOK, progressCallback,
&myProg ); //this is the arg for our custom gdal progress callback
if ( myError != CE_None )
{
QgsDebugMsg( "Cannot get histogram" );
delete [] myHistogramArray;
return myHistogram;
}

#endif

for ( int myBin = 0; myBin < theBinCount; myBin++ )
for ( int myBin = 0; myBin < myHistogram.binCount; myBin++ )
{
if ( myHistogramArray[myBin] < 0 ) //can't have less than 0 pixels of any value
{
if ( myHistogramArray[myBin] < 0 ) //can't have less than 0 pixels of any value
{
theBandStats.histogramVector->push_back( 0 );
// QgsDebugMsg( "Added 0 to histogram vector as freq was negative!" );
}
else
{
theBandStats.histogramVector->push_back( myHistogramArray[myBin] );
// QgsDebugMsg( "Added " + QString::number( myHistogramArray[myBin] ) + " to histogram vector" );
}
myHistogram.histogramVector.push_back( 0 );
// QgsDebugMsg( "Added 0 to histogram vector as freq was negative!" );
}
delete [] myHistogramArray;
else
{
myHistogram.histogramVector.push_back( myHistogramArray[myBin] );
myHistogram.nonNullCount += myHistogramArray[myBin];
// QgsDebugMsg( "Added " + QString::number( myHistogramArray[myBin] ) + " to histogram vector" );
}
}

myHistogram.valid = true;

}
QgsDebugMsg( ">>>>> Histogram vector now contains " + QString::number( theBandStats.histogramVector->size() ) +
" elements" );
delete [] myHistogramArray;

QgsDebugMsg( ">>>>> Histogram vector now contains " + QString::number( myHistogram.histogramVector.size() ) + " elements" );

mHistograms.append( myHistogram );
return myHistogram;
}

/*
Expand Down Expand Up @@ -1913,14 +2001,47 @@ QGISEXTERN bool isValidRasterFileName( QString const & theFileNameQString, QStri
}
}

QgsRasterBandStats QgsGdalProvider::bandStatistics( int theBandNo, const QgsRectangle & theExtent, int theSampleSize )
{
QgsDebugMsg( QString( "theBandNo = %1 theSampleSize = %2" ).arg( theBandNo ).arg( theSampleSize ) );
// Currently there is no API in GDAL to collect statistics of specified extent
// or with defined sample size. We check first if we have cached stats, if not,
// and it is not possible to use GDAL we call generic provider method,
// otherwise we use GDAL (faster, cache)

QgsRasterBandStats myRasterBandStats = statisticsDefaults( theBandNo, theExtent, theSampleSize );

QgsRasterBandStats QgsGdalProvider::bandStatistics( int theBandNo )
{
foreach( QgsRasterBandStats stats, mStatistics )
{
if ( stats == myRasterBandStats )
{
QgsDebugMsg( "Using cached statistics." );
return stats;
}
}

if ( myRasterBandStats.extent != extent() )
{
QgsDebugMsg( "Using generic statistics." );
return QgsRasterDataProvider::bandStatistics( theBandNo, theExtent, theSampleSize );
}

QgsDebugMsg( "Using GDAL statistics." );
GDALRasterBandH myGdalBand = GDALGetRasterBand( mGdalDataset, theBandNo );
QgsRasterBandStats myRasterBandStats;
// int bApproxOK = true;
int bApproxOK = false; //as we asked for stats, don't get approx values

//int bApproxOK = false; //as we asked for stats, don't get approx values
// GDAL does not have sample size parameter in API, just bApproxOK or not,
// we decide if approximation should be used according to
// total size / sample size ration
int bApproxOK = false;
if ( theSampleSize > 0 )
{
if (( xSize() * ySize() / theSampleSize ) > 2 ) // not perfect
{
bApproxOK = true;
}
}

double pdfMin;
double pdfMax;
double pdfMean;
Expand Down Expand Up @@ -1972,6 +2093,7 @@ QgsRasterBandStats QgsGdalProvider::bandStatistics( int theBandNo )

}

mStatistics.append( myRasterBandStats );
return myRasterBandStats;

} // QgsGdalProvider::bandStatistics
Expand Down
28 changes: 19 additions & 9 deletions src/providers/gdal/qgsgdalprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,25 @@ class QgsGdalProvider : public QgsRasterDataProvider
@note overloads virtual method from QgsRasterProvider::bandStatistics
*/
QgsRasterBandStats bandStatistics( int theBandNo );

bool hasCachedHistogram( int theBandNoInt, int theBinCountInt = RASTER_HISTOGRAM_BINS );
void populateHistogram( int theBandNoInt,
QgsRasterBandStats & theBandStats,
int theBinCountInt = RASTER_HISTOGRAM_BINS,
bool theIgnoreOutOfRangeFlag = true,
bool theThoroughBandScanFlag = false
);
QgsRasterBandStats bandStatistics( int theBandNo,
const QgsRectangle & theExtent = QgsRectangle(),
int theSampleSize = 0 );

bool hasHistogram( int theBandNo,
int theBinCount = 0,

This comment has been minimized.

Copy link
@etiennesky

etiennesky Jul 26, 2012

Contributor

default should not be 256, at least for gdal driver?

This comment has been minimized.

Copy link
@blazek

blazek Jul 27, 2012

Author Member

theBinCount = 0 means not defined, decide automatically, for all drivers it defaults to 256 for byte and 1000 for other types.

double theMinimum = std::numeric_limits<double>::quiet_NaN(),
double theMaximum = std::numeric_limits<double>::quiet_NaN(),
const QgsRectangle & theExtent = QgsRectangle(),
int theSampleSize = 0,
bool theIncludeOutOfRange = false );

QgsRasterHistogram histogram( int theBandNo,
int theBinCount = 0,
double theMinimum = std::numeric_limits<double>::quiet_NaN(),
double theMaximum = std::numeric_limits<double>::quiet_NaN(),
const QgsRectangle & theExtent = QgsRectangle(),
int theSampleSize = 0,
bool theIncludeOutOfRange = false );

QString buildPyramids( const QList<QgsRasterPyramid> &,
const QString & theResamplingMethod = "NEAREST",
Expand Down
27 changes: 0 additions & 27 deletions src/providers/grass/qgsgrassrasterprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -443,33 +443,6 @@ QString QgsGrassRasterProvider::metadata()
return myMetadata;
}

void QgsGrassRasterProvider::populateHistogram( int theBandNoInt,
QgsRasterBandStats & theBandStats,
int theBinCount,
bool theIgnoreOutOfRangeFlag,
bool theHistogramEstimatedFlag )
{
Q_UNUSED( theBandNoInt );
// TODO: we could either implement it in QgsRasterDataProvider::populateHistogram
// or use r.stats (see d.histogram)
if ( theBandStats.histogramVector->size() != theBinCount ||
theIgnoreOutOfRangeFlag != theBandStats.isHistogramOutOfRange ||
theHistogramEstimatedFlag != theBandStats.isHistogramEstimated )
{
theBandStats.histogramVector->clear();
theBandStats.isHistogramEstimated = theHistogramEstimatedFlag;
theBandStats.isHistogramOutOfRange = theIgnoreOutOfRangeFlag;
for ( int myBin = 0; myBin < theBinCount; myBin++ )
{
theBandStats.histogramVector->push_back( 0 );
}
}
QgsDebugMsg( ">>>>> Histogram vector now contains " +
QString::number( theBandStats.histogramVector->size() ) + " elements" );

}


bool QgsGrassRasterProvider::isValid()
{
return mValid;
Expand Down
7 changes: 0 additions & 7 deletions src/providers/grass/qgsgrassrasterprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,6 @@ class QgsGrassRasterProvider : public QgsRasterDataProvider
void setImageCrs( QString const &crs )
{ Q_UNUSED( crs ); }

void populateHistogram( int theBandNoInt,
QgsRasterBandStats & theBandStats,
int theBinCountInt = 256,
bool theIgnoreOutOfRangeFlag = true,
bool theThoroughBandScanFlag = false
);

virtual QDateTime dataTimestamp() const;
private:

Expand Down