| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 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, | ||
| 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", | ||
|
|
@@ -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 ); | ||
|
|
||
| /** \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 | ||
|
|
@@ -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.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
blazek
Author
Member
|
||
| 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -215,7 +215,9 @@ bool QgsRasterHistogramWidget::computeHistogram( bool forceComputeFlag ) | |
| myIteratorInt <= myBandCountInt; | ||
| ++myIteratorInt ) | ||
| { | ||
| //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.
Sorry, something went wrong.
etiennesky
Contributor
|
||
| { | ||
| QgsDebugMsg( QString( "band %1 does not have cached histo" ).arg( myIteratorInt ) ); | ||
| return false; | ||
|
|
@@ -232,7 +234,9 @@ bool QgsRasterHistogramWidget::computeHistogram( bool forceComputeFlag ) | |
| myIteratorInt <= myBandCountInt; | ||
| ++myIteratorInt ) | ||
| { | ||
| //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 ) ) ); | ||
|
|
@@ -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 ); | ||
|
|
@@ -425,7 +432,8 @@ void QgsRasterHistogramWidget::refreshHistogram() | |
|
|
||
| for ( int myBin = 0; myBin < BINCOUNT; 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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1243,124 +1243,212 @@ QStringList QgsGdalProvider::subLayers( GDALDatasetH dataset ) | |
| return subLayers; | ||
| } | ||
|
|
||
| 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; | ||
|
|
||
| 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; | ||
| } | ||
|
|
||
| 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 ); | ||
|
|
||
| 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 | ||
|
|
||
| #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 | ||
|
|
||
| // 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" ) ); | ||
| if ( GDALGetRasterDataType( myGdalBand ) == GDT_Byte && !bSignedByte ) | ||
This comment has been minimized.
Sorry, something went wrong.
etiennesky
Contributor
|
||
| { | ||
| 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 < myHistogram.binCount; myBin++ ) | ||
| { | ||
| if ( myHistogramArray[myBin] < 0 ) //can't have less than 0 pixels of any value | ||
| { | ||
| myHistogram.histogramVector.push_back( 0 ); | ||
| // QgsDebugMsg( "Added 0 to histogram vector as freq was negative!" ); | ||
| } | ||
| else | ||
| { | ||
| myHistogram.histogramVector.push_back( myHistogramArray[myBin] ); | ||
| myHistogram.nonNullCount += myHistogramArray[myBin]; | ||
| // QgsDebugMsg( "Added " + QString::number( myHistogramArray[myBin] ) + " to histogram vector" ); | ||
| } | ||
| } | ||
|
|
||
| myHistogram.valid = true; | ||
|
|
||
| delete [] myHistogramArray; | ||
|
|
||
| QgsDebugMsg( ">>>>> Histogram vector now contains " + QString::number( myHistogram.histogramVector.size() ) + " elements" ); | ||
|
|
||
| mHistograms.append( myHistogram ); | ||
| return myHistogram; | ||
| } | ||
|
|
||
| /* | ||
|
|
@@ -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 ); | ||
|
|
||
| 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 ); | ||
|
|
||
| //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; | ||
|
|
@@ -1972,6 +2093,7 @@ QgsRasterBandStats QgsGdalProvider::bandStatistics( int theBandNo ) | |
|
|
||
| } | ||
|
|
||
| mStatistics.append( myRasterBandStats ); | ||
| return myRasterBandStats; | ||
|
|
||
| } // QgsGdalProvider::bandStatistics | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -244,15 +244,25 @@ class QgsGdalProvider : public QgsRasterDataProvider | |
| @note overloads virtual method from QgsRasterProvider::bandStatistics | ||
| */ | ||
| QgsRasterBandStats bandStatistics( int theBandNo, | ||
| const QgsRectangle & theExtent = QgsRectangle(), | ||
| int theSampleSize = 0 ); | ||
|
|
||
| bool hasHistogram( int theBandNo, | ||
| int theBinCount = 0, | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
blazek
Author
Member
|
||
| 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", | ||
|
|
||
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"