QgsRasterBandStats QgsRasterInterface::bandStatistics( int theBandNo, int theStats, const QgsRectangle & theExtent, int theSampleSize ) { QgsDebugMsg( QString( "theBandNo = %1 theStats = %2 theSampleSize = %3" ).arg( theBandNo ).arg( theStats ).arg( theSampleSize ) ); // TODO: null values set on raster layer!!! QgsRasterBandStats myRasterBandStats; initStatistics( myRasterBandStats, theBandNo, theStats, theExtent, theSampleSize ); foreach ( QgsRasterBandStats stats, mStatistics ) { if ( stats.contains( myRasterBandStats ) ) { QgsDebugMsg( "Using cached statistics." ); return stats; } } QgsRectangle myExtent = myRasterBandStats.extent; int myWidth = myRasterBandStats.width; int myHeight = myRasterBandStats.height; //int myDataType = dataType( theBandNo ); int myXBlockSize = xBlockSize(); int myYBlockSize = yBlockSize(); if ( myXBlockSize == 0 ) // should not happen, but happens { myXBlockSize = 500; } if ( myYBlockSize == 0 ) // should not happen, but happens { myYBlockSize = 500; } int myNXBlocks = ( myWidth + myXBlockSize - 1 ) / myXBlockSize; int myNYBlocks = ( myHeight + myYBlockSize - 1 ) / myYBlockSize; double myXRes = myExtent.width() / myWidth; double myYRes = myExtent.height() / myHeight; // TODO: progress signals // used by single pass stdev double myMean = 0; double mySumOfSquares = 0; bool myFirstIterationFlag = true; for ( int myYBlock = 0; myYBlock < myNYBlocks; myYBlock++ ) { for ( int myXBlock = 0; myXBlock < myNXBlocks; myXBlock++ ) { QgsDebugMsg( QString( "myYBlock = %1 myXBlock = %2" ).arg( myYBlock ).arg( myXBlock ) ); int myBlockWidth = qMin( myXBlockSize, myWidth - myXBlock * myXBlockSize ); int myBlockHeight = qMin( myYBlockSize, myHeight - myYBlock * myYBlockSize ); double xmin = myExtent.xMinimum() + myXBlock * myXBlockSize * myXRes; double xmax = xmin + myBlockWidth * myXRes; double ymin = myExtent.yMaximum() - myYBlock * myYBlockSize * myYRes; double ymax = ymin - myBlockHeight * myYRes; QgsRectangle myPartExtent( xmin, ymin, xmax, ymax ); QgsRasterBlock* blk = block( theBandNo, myPartExtent, myBlockWidth, myBlockHeight ); // Collect the histogram counts. for ( qgssize i = 0; i < (( qgssize ) myBlockHeight ) * myBlockWidth; i++ ) { if ( blk->isNoData( i ) ) continue; // NULL double myValue = blk->value( i ); myRasterBandStats.sum += myValue; myRasterBandStats.elementCount++; if ( myFirstIterationFlag ) { myFirstIterationFlag = false; myRasterBandStats.minimumValue = myValue; myRasterBandStats.maximumValue = myValue; } else { if ( myValue < myRasterBandStats.minimumValue ) { myRasterBandStats.minimumValue = myValue; } if ( myValue > myRasterBandStats.maximumValue ) { myRasterBandStats.maximumValue = myValue; } } // Single pass stdev double myDelta = myValue - myMean; myMean += myDelta / myRasterBandStats.elementCount; mySumOfSquares += myDelta * ( myValue - myMean ); } delete blk; } } myRasterBandStats.range = myRasterBandStats.maximumValue - myRasterBandStats.minimumValue; myRasterBandStats.mean = myRasterBandStats.sum / myRasterBandStats.elementCount; myRasterBandStats.sumOfSquares = mySumOfSquares; // OK with single pass? // stdDev may differ from GDAL stats, because GDAL is using naive single pass // algorithm which is more error prone (because of rounding errors) // Divide result by sample size - 1 and get square root to get stdev myRasterBandStats.stdDev = sqrt( mySumOfSquares / ( myRasterBandStats.elementCount - 1 ) ); QgsDebugMsg( "************ STATS **************" ); QgsDebugMsg( QString( "MIN %1" ).arg( myRasterBandStats.minimumValue ) ); QgsDebugMsg( QString( "MAX %1" ).arg( myRasterBandStats.maximumValue ) ); QgsDebugMsg( QString( "RANGE %1" ).arg( myRasterBandStats.range ) ); QgsDebugMsg( QString( "MEAN %1" ).arg( myRasterBandStats.mean ) ); QgsDebugMsg( QString( "STDDEV %1" ).arg( myRasterBandStats.stdDev ) ); myRasterBandStats.statsGathered = QgsRasterBandStats::All; mStatistics.append( myRasterBandStats ); return myRasterBandStats; }
QgsRasterHistogram QgsRasterInterface::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; initHistogram( myHistogram, theBandNo, theBinCount, theMinimum, theMaximum, theExtent, theSampleSize, theIncludeOutOfRange ); // Find cached foreach ( QgsRasterHistogram histogram, mHistograms ) { if ( histogram == myHistogram ) { QgsDebugMsg( "Using cached histogram." ); return histogram; } } int myBinCount = myHistogram.binCount; int myWidth = myHistogram.width; int myHeight = myHistogram.height; QgsRectangle myExtent = myHistogram.extent; myHistogram.histogramVector.resize( myBinCount ); int myXBlockSize = xBlockSize(); int myYBlockSize = yBlockSize(); if ( myXBlockSize == 0 ) // should not happen, but happens { myXBlockSize = 500; } if ( myYBlockSize == 0 ) // should not happen, but happens { myYBlockSize = 500; } int myNXBlocks = ( myWidth + myXBlockSize - 1 ) / myXBlockSize; int myNYBlocks = ( myHeight + myYBlockSize - 1 ) / myYBlockSize; double myXRes = myExtent.width() / myWidth; double myYRes = myExtent.height() / myHeight; double myMinimum = myHistogram.minimum; double myMaximum = myHistogram.maximum; // To avoid rounding errors // TODO: check this double myerval = ( myMaximum - myMinimum ) / myHistogram.binCount; myMinimum -= 0.1 * myerval; myMaximum += 0.1 * myerval; QgsDebugMsg( QString( "binCount = %1 myMinimum = %2 myMaximum = %3" ).arg( myHistogram.binCount ).arg( myMinimum ).arg( myMaximum ) ); double myBinSize = ( myMaximum - myMinimum ) / myBinCount; // TODO: progress signals for ( int myYBlock = 0; myYBlock < myNYBlocks; myYBlock++ ) { for ( int myXBlock = 0; myXBlock < myNXBlocks; myXBlock++ ) { int myBlockWidth = qMin( myXBlockSize, myWidth - myXBlock * myXBlockSize ); int myBlockHeight = qMin( myYBlockSize, myHeight - myYBlock * myYBlockSize ); double xmin = myExtent.xMinimum() + myXBlock * myXBlockSize * myXRes; double xmax = xmin + myBlockWidth * myXRes; double ymin = myExtent.yMaximum() - myYBlock * myYBlockSize * myYRes; double ymax = ymin - myBlockHeight * myYRes; QgsRectangle myPartExtent( xmin, ymin, xmax, ymax ); QgsRasterBlock* blk = block( theBandNo, myPartExtent, myBlockWidth, myBlockHeight ); // Collect the histogram counts. for ( qgssize i = 0; i < (( qgssize ) myBlockHeight ) * myBlockWidth; i++ ) { if ( blk->isNoData( i ) ) { continue; // NULL } double myValue = blk->value( i ); int myBinIndex = static_cast <int>( qFloor(( myValue - myMinimum ) / myBinSize ) ) ; if (( myBinIndex < 0 || myBinIndex > ( myBinCount - 1 ) ) && !theIncludeOutOfRange ) { continue; } if ( myBinIndex < 0 ) myBinIndex = 0; if ( myBinIndex > ( myBinCount - 1 ) ) myBinIndex = myBinCount - 1; myHistogram.histogramVector[myBinIndex] += 1; myHistogram.nonNullCount++; } delete blk; } } myHistogram.valid = true; mHistograms.append( myHistogram ); #ifdef QGISDEBUG QString hist; for ( int i = 0; i < qMin( myHistogram.histogramVector.size(), 500 ); i++ ) { hist += QString::number( myHistogram.histogramVector.value( i ) ) + " "; } QgsDebugMsg( "Histogram (max first 500 bins): " + hist ); #endif return myHistogram; }
QgsRasterBandStats QgsRasterDataProvider::bandStatistics( int theBandNo ) { double myNoDataValue = noDataValue(); QgsRasterBandStats myRasterBandStats; myRasterBandStats.elementCount = 0; // because we'll be counting only VALID pixels later myRasterBandStats.bandName = generateBandName( theBandNo ); myRasterBandStats.bandNumber = theBandNo; int myDataType = dataType( theBandNo ); int myNXBlocks, myNYBlocks, myXBlockSize, myYBlockSize; myXBlockSize = xBlockSize(); myYBlockSize = yBlockSize(); myNXBlocks = ( xSize() + myXBlockSize - 1 ) / myXBlockSize; myNYBlocks = ( ySize() + myYBlockSize - 1 ) / myYBlockSize; void *myData = CPLMalloc( myXBlockSize * myYBlockSize * ( dataTypeSize( theBandNo ) / 8 ) ); // unfortunately we need to make two passes through the data to calculate stddev bool myFirstIterationFlag = true; int myBandXSize = xSize(); int myBandYSize = ySize(); for ( int iYBlock = 0; iYBlock < myNYBlocks; iYBlock++ ) { for ( int iXBlock = 0; iXBlock < myNXBlocks; iXBlock++ ) { int nXValid, nYValid; readBlock( theBandNo, iXBlock, iYBlock, myData ); // Compute the portion of the block that is valid // for partial edge blocks. if (( iXBlock + 1 ) * myXBlockSize > myBandXSize ) nXValid = myBandXSize - iXBlock * myXBlockSize; else nXValid = myXBlockSize; if (( iYBlock + 1 ) * myYBlockSize > myBandYSize ) nYValid = myBandYSize - iYBlock * myYBlockSize; else nYValid = myYBlockSize; // Collect the histogram counts. for ( int iY = 0; iY < nYValid; iY++ ) { for ( int iX = 0; iX < nXValid; iX++ ) { double myValue = readValue( myData, myDataType, iX + ( iY * myXBlockSize ) ); //QgsDebugMsg ( QString ( "%1 %2 value %3" ).arg (iX).arg(iY).arg( myValue ) ); if ( mValidNoDataValue && ( qAbs( myValue - myNoDataValue ) <= TINY_VALUE ) ) { continue; // NULL } myRasterBandStats.sum += myValue; ++myRasterBandStats.elementCount; //only use this element if we have a non null element if ( myFirstIterationFlag ) { //this is the first iteration so initialise vars myFirstIterationFlag = false; myRasterBandStats.minimumValue = myValue; myRasterBandStats.maximumValue = myValue; } //end of true part for first iteration check else { //this is done for all subsequent iterations if ( myValue < myRasterBandStats.minimumValue ) { myRasterBandStats.minimumValue = myValue; } if ( myValue > myRasterBandStats.maximumValue ) { myRasterBandStats.maximumValue = myValue; } } //end of false part for first iteration check } } } //end of column wise loop } //end of row wise loop //end of first pass through data now calculate the range myRasterBandStats.range = myRasterBandStats.maximumValue - myRasterBandStats.minimumValue; //calculate the mean myRasterBandStats.mean = myRasterBandStats.sum / myRasterBandStats.elementCount; //for the second pass we will get the sum of the squares / mean for ( int iYBlock = 0; iYBlock < myNYBlocks; iYBlock++ ) { for ( int iXBlock = 0; iXBlock < myNXBlocks; iXBlock++ ) { int nXValid, nYValid; readBlock( theBandNo, iXBlock, iYBlock, myData ); // Compute the portion of the block that is valid // for partial edge blocks. if (( iXBlock + 1 ) * myXBlockSize > myBandXSize ) nXValid = myBandXSize - iXBlock * myXBlockSize; else nXValid = myXBlockSize; if (( iYBlock + 1 ) * myYBlockSize > myBandYSize ) nYValid = myBandYSize - iYBlock * myYBlockSize; else nYValid = myYBlockSize; // Collect the histogram counts. for ( int iY = 0; iY < nYValid; iY++ ) { for ( int iX = 0; iX < nXValid; iX++ ) { double myValue = readValue( myData, myDataType, iX + ( iY * myXBlockSize ) ); //QgsDebugMsg ( "myValue = " + QString::number(myValue) ); if ( mValidNoDataValue && ( qAbs( myValue - myNoDataValue ) <= TINY_VALUE ) ) { continue; // NULL } myRasterBandStats.sumOfSquares += static_cast < double > ( pow( myValue - myRasterBandStats.mean, 2 ) ); } } } //end of column wise loop } //end of row wise loop //divide result by sample size - 1 and get square root to get stdev myRasterBandStats.stdDev = static_cast < double >( sqrt( myRasterBandStats.sumOfSquares / ( myRasterBandStats.elementCount - 1 ) ) ); #ifdef QGISDEBUG QgsLogger::debug( "************ STATS **************", 1, __FILE__, __FUNCTION__, __LINE__ ); QgsLogger::debug( "VALID NODATA", mValidNoDataValue, 1, __FILE__, __FUNCTION__, __LINE__ ); QgsLogger::debug( "NULL", noDataValue() , 1, __FILE__, __FUNCTION__, __LINE__ ); QgsLogger::debug( "MIN", myRasterBandStats.minimumValue, 1, __FILE__, __FUNCTION__, __LINE__ ); QgsLogger::debug( "MAX", myRasterBandStats.maximumValue, 1, __FILE__, __FUNCTION__, __LINE__ ); QgsLogger::debug( "RANGE", myRasterBandStats.range, 1, __FILE__, __FUNCTION__, __LINE__ ); QgsLogger::debug( "MEAN", myRasterBandStats.mean, 1, __FILE__, __FUNCTION__, __LINE__ ); QgsLogger::debug( "STDDEV", myRasterBandStats.stdDev, 1, __FILE__, __FUNCTION__, __LINE__ ); #endif CPLFree( myData ); myRasterBandStats.statsGathered = true; return myRasterBandStats; }