New QgsRasterChecker class for raster comparison in tests.

New test for raster file writer.
This commit is contained in:
Radim Blazek 2012-09-05 17:11:03 +02:00
parent 8cb578f698
commit e1091013af
6 changed files with 504 additions and 204 deletions

View File

@ -169,6 +169,7 @@ SET(QGIS_CORE_SRCS
raster/qgslinearminmaxenhancement.cpp
raster/qgslinearminmaxenhancementwithclip.cpp
raster/qgspseudocolorshader.cpp
raster/qgsrasterchecker.cpp
raster/qgsrasterinterface.cpp
raster/qgsrasteriterator.cpp
raster/qgsrasterlayer.cpp
@ -411,6 +412,7 @@ SET(QGIS_CORE_HDRS
raster/qgslinearminmaxenhancement.h
raster/qgslinearminmaxenhancementwithclip.h
raster/qgspseudocolorshader.h
raster/qgsrasterchecker.h
raster/qgsrasterpyramid.h
raster/qgsrasterbandstats.h
raster/qgsrasterhistogram.h

View File

@ -0,0 +1,236 @@
/***************************************************************************
qgsrasterchecker.cpp
--------------------------------------
Date : 5 Sep 2012
Copyright : (C) 2012 by Radim Blazek
Email : radim dot blazek at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsrasterchecker.h"
#include "qgsrasterdataprovider.h"
#include "qgsrasterlayer.h"
#include <qmath.h>
#include <QColor>
#include <QPainter>
#include <QImage>
#include <QTime>
#include <QCryptographicHash>
#include <QByteArray>
#include <QDebug>
#include <QBuffer>
QgsRasterChecker::QgsRasterChecker( ) :
mReport( "" )
{
mTabStyle = "border-spacing: 0px; border-width: 1px 1px 0 0; border-style: solid;";
mCellStyle = "border-width: 0 0 1px 1px; border-style: solid; font-size: smaller; text-align: center;";
mOkStyle = "background: #00ff00;";
mErrStyle = "background: #ff0000;";
mErrMsgStyle = "color: #ff0000;";
}
bool QgsRasterChecker::runTest( QString theVerifiedKey, QString theVerifiedUri,
QString theExpectedKey, QString theExpectedUri )
{
bool ok = true;
mReport += "\n\n";
QgsRasterDataProvider* verifiedProvider = QgsRasterLayer::loadProvider( theVerifiedKey, theVerifiedUri );
if ( !verifiedProvider || !verifiedProvider->isValid() )
{
error( QString( "Cannot load provider %1 with URI: %2" ).arg( theVerifiedKey).arg( theVerifiedUri), mReport );
ok = false;
}
QgsRasterDataProvider* expectedProvider = QgsRasterLayer::loadProvider( theExpectedKey, theExpectedUri );
if ( !expectedProvider || !expectedProvider->isValid() )
{
error( QString( "Cannot load provider %1 with URI: %2" ).arg( theExpectedKey).arg( theExpectedUri ), mReport );
ok = false;
}
if ( !ok ) return false;
mReport += QString( "Verified URI: %1<br>" ).arg( theVerifiedUri.replace( "&", "&amp;" ) );
mReport += QString( "Expected URI: %1<br>" ).arg( theExpectedUri.replace( "&", "&amp;" ) );
mReport += "<br>";
mReport += QString("<table style='%1'>\n").arg( mTabStyle );
mReport += compareHead();
compare( "Band count", verifiedProvider->bandCount(), expectedProvider->bandCount(), mReport, ok );
compare( "Width", verifiedProvider->xSize(), expectedProvider->xSize(), mReport, ok );
compare( "Height", verifiedProvider->ySize(), expectedProvider->ySize(), mReport, ok );
compareRow( "Extent", verifiedProvider->extent().toString(), expectedProvider->extent().toString(), mReport, verifiedProvider->extent() == expectedProvider->extent() );
if ( verifiedProvider->extent() != expectedProvider->extent() ) ok = false;
compare( "No data (NULL) value", verifiedProvider->noDataValue(), expectedProvider->noDataValue(), mReport, ok );
mReport += "</table>\n";
if ( !ok ) return false;
bool allOk = true;
for ( int band = 1; band <= expectedProvider->bandCount(); band++ )
{
bool bandOk = true;
mReport += QString( "<h3>Band %1</h3>\n" ).arg( band );
mReport += QString("<table style='%1'>\n").arg( mTabStyle );
mReport += compareHead();
// Data types may differ (?)
bool typesOk = true;
compare( "Source data type", verifiedProvider->srcDataType( band ), expectedProvider->srcDataType( band ), mReport, typesOk );
compare( "Data type", verifiedProvider->dataType( band ), expectedProvider->dataType( band ), mReport, typesOk ) ;
bool statsOk = true;
QgsRasterBandStats verifiedStats = verifiedProvider->bandStatistics( band );
QgsRasterBandStats expectedStats = expectedProvider->bandStatistics( band );
// Min/max may 'slightly' differ, for big numbers however, the difference may
// be quite big, for example for Float32 with max -3.332e+38, the difference is 1.47338e+24
double tol = tolerance( expectedStats.minimumValue );
compare( "Minimum value", verifiedStats.minimumValue, expectedStats.minimumValue, mReport, statsOk, tol );
tol = tolerance( expectedStats.maximumValue );
compare( "Maximum value", verifiedStats.maximumValue, expectedStats.maximumValue, mReport, statsOk, tol );
// TODO: enable once fixed (WCS excludes nulls but GDAL does not)
//compare( "Cells count", verifiedStats.elementCount, expectedStats.elementCount, mReport, statsOk );
tol = tolerance( expectedStats.mean );
compare( "Mean", verifiedStats.mean, expectedStats.mean, mReport, statsOk, tol );
// stdDev usually differ significantly
tol = tolerance( expectedStats.stdDev, 1 );
compare( "Standard deviation", verifiedStats.stdDev, expectedStats.stdDev, mReport, statsOk, tol );
mReport += "</table>";
mReport += "<br>";
if ( !bandOk )
{
allOk = false;
continue;
}
if ( !statsOk || !typesOk )
{
allOk = false;
// create values table anyway so that values are available
}
mReport += "<table><tr>";
mReport += "<td>Data comparison</td>";
mReport += QString( "<td style='%1 %2 border: 1px solid'>correct&nbsp;value</td>" ).arg( mCellStyle).arg( mOkStyle );
mReport += "<td></td>";
mReport += QString( "<td style='%1 %2 border: 1px solid'>wrong&nbsp;value<br>expected value</td></tr>").arg( mCellStyle).arg( mErrStyle );
mReport += "</tr></table>";
mReport += "<br>";
int width = expectedProvider->xSize();
int height = expectedProvider->ySize();
int blockSize = width * height * expectedProvider->typeSize( expectedProvider->dataType( band ) ) ;
void * expectedData = malloc( blockSize );
void * verifiedData = malloc( blockSize );
expectedProvider->readBlock( band, expectedProvider->extent(), width, height, expectedData );
verifiedProvider->readBlock( band, expectedProvider->extent(), width, height, verifiedData );
// compare data values
QString htmlTable = QString("<table style='%1'>").arg( mTabStyle );
for ( int row = 0; row < height; row ++ )
{
htmlTable += "<tr>";
for ( int col = 0; col < width; col ++ )
{
bool cellOk = true;
double verifiedVal = verifiedProvider->readValue( verifiedData, verifiedProvider->dataType( band ), row * width + col );
double expectedVal = expectedProvider->readValue( expectedData, expectedProvider->dataType( band ), row * width + col );
QString valStr;
if ( compare( verifiedVal, expectedVal, 0 ) )
{
valStr = QString( "%1" ).arg( verifiedVal );
}
else
{
cellOk = false;
allOk = false;
valStr = QString( "%1<br>%2" ).arg( verifiedVal ).arg( expectedVal );
}
htmlTable += QString( "<td style='%1 %2'>%3</td>" ).arg( mCellStyle).arg( cellOk ? mOkStyle : mErrStyle ).arg( valStr );
}
htmlTable += "</tr>";
}
htmlTable += "</table>";
mReport += htmlTable;
free( expectedData );
free( verifiedData );
}
delete verifiedProvider;
delete expectedProvider;
return allOk;
}
void QgsRasterChecker::error( QString theMessage, QString &theReport )
{
theReport += QString( "<font style='%1'>Error: ").arg(mErrMsgStyle);
theReport += theMessage;
theReport += "</font>";
}
double QgsRasterChecker::tolerance( double val, int places )
{
// float precision is about 7 decimal digits, double about 16
// default places = 6
return 1. * qPow( 10, qRound( log10( qAbs( val ) ) - places ) );
}
QString QgsRasterChecker::compareHead()
{
QString html;
html += QString( "<tr><th style='%1'>Param name</th><th style='%1'>Verified value</th><th style='%1'>Eexpected value</th><th style='%1'>Difference</th><th style='%1'>Tolerance</th></tr>").arg( mCellStyle );
return html;
}
void QgsRasterChecker::compare( QString theParamName, int verifiedVal, int expectedVal, QString &theReport, bool &theOk )
{
bool ok = verifiedVal == expectedVal;
compareRow( theParamName, QString::number( verifiedVal ), QString::number( expectedVal ), theReport, ok, QString::number( verifiedVal - expectedVal ) );
if ( !ok ) theOk = false;
}
bool QgsRasterChecker::compare( double verifiedVal, double expectedVal, double theTolerance )
{
// values may be nan
return ( qIsNaN( verifiedVal ) && qIsNaN( expectedVal ) ) || ( qAbs( verifiedVal - expectedVal ) <= theTolerance );
}
void QgsRasterChecker::compare( QString theParamName, double verifiedVal, double expectedVal, QString &theReport, bool &theOk, double theTolerance )
{
bool ok = compare( verifiedVal, expectedVal, theTolerance );
compareRow( theParamName, QString::number( verifiedVal ), QString::number( expectedVal ), theReport, ok, QString::number( verifiedVal - expectedVal ), QString::number( theTolerance ) );
if ( !ok ) theOk = false;
}
void QgsRasterChecker::compareRow( QString theParamName, QString verifiedVal, QString expectedVal, QString &theReport, bool theOk, QString theDifference, QString theTolerance )
{
theReport += "<tr>\n";
theReport += QString( "<td style='%1'>%2</td><td style='%1 %3'>%4</td><td style='%1'>%5</td>\n" ).arg(mCellStyle).arg( theParamName ).arg( theOk ? mOkStyle : mErrStyle ).arg( verifiedVal ).arg( expectedVal );
theReport += QString( "<td style='%1'>%2</td>\n" ).arg(mCellStyle).arg( theDifference );
theReport += QString( "<td style='%1'>%2</td>\n" ).arg(mCellStyle).arg( theTolerance );
theReport += "</tr>";
}

View File

@ -0,0 +1,71 @@
/***************************************************************************
qgsrasterchecker.h - compare two rasters
--------------------------------------
Date : 5 Sep 2012
Copyright : (C) 2012 by Radim Blazek
email : radim dot blazek at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSRASTERCHECKER_H
#define QGSRASTERCHECKER_H
#include <QDir>
#include <QString>
#include <qgsmaprenderer.h>
class QImage;
/** \ingroup UnitTests
* This is a helper class for unit tests that need to
* write an image and compare it to an expected result
* or render time.
*/
class CORE_EXPORT QgsRasterChecker
{
public:
QgsRasterChecker();
//! Destructor
~QgsRasterChecker() {};
QString controlImagePath() const;
QString report() { return mReport; };
/**
* Test using renderer to generate the image to be compared.
* @param theVerifiedKey verified provider key
* @param theVerifiedUri URI of the raster to be verified
* @param theExpectedKey expected provider key
* @param theExpectedUri URI of the expected (control) raster
*/
bool runTest( QString theVerifiedKey, QString theVerifiedUri,
QString theExpectedKey, QString theExpectedUri );
private:
QString mReport;
QString mExpectedUri;
QString mCheckedUri;
QString mTabStyle;
QString mCellStyle;
QString mOkStyle;
QString mErrStyle;
QString mErrMsgStyle;
// Log error in html
void error( QString theMessage, QString &theReport );
// compare values and add table row in html report, set ok to false if not equal
QString compareHead();
bool compare( double verifiedVal, double expectedVal, double theTolerance );
void compare( QString theParamName, int verifiedVal, int expectedVal, QString &theReport, bool &theOk );
void compare( QString theParamName, double verifiedVal, double expectedVal, QString &theReport, bool &theOk, double theTolerance = 0 );
void compareRow( QString theParamName, QString verifiedVal, QString expectedVal, QString &theReport, bool theOk, QString theDifference = "", QString theTolerance = "" );
double tolerance( double val, int places = 6 );
}; // class QgsRasterChecker
#endif

View File

@ -85,6 +85,7 @@ ADD_QGIS_TEST(filewritertest testqgsvectorfilewriter.cpp)
ADD_QGIS_TEST(regression992 regression992.cpp)
ADD_QGIS_TEST(regression1141 regression1141.cpp)
ADD_QGIS_TEST(rasterlayertest testqgsrasterlayer.cpp)
ADD_QGIS_TEST(rasterfilewritertest testqgsrasterfilewriter.cpp)
ADD_QGIS_TEST(contrastenhancementtest testcontrastenhancements.cpp)
ADD_QGIS_TEST(maplayertest testqgsmaplayer.cpp)
ADD_QGIS_TEST(rendererstest testqgsrenderers.cpp)

View File

@ -0,0 +1,184 @@
/***************************************************************************
testqgsrasterfilewriter.cpp
--------------------------------------
Date : 5 Sep 2012
Copyright : (C) 2012 by Radim Blazek
Email : radim dot blazek at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <QtTest>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QObject>
#include <iostream>
#include <QApplication>
#include <QFileInfo>
#include <QDir>
#include <QPainter>
#include <QTime>
#include <QDesktopServices>
//qgis includes...
#include <qgsrasterchecker.h>
#include <qgsrasterlayer.h>
#include <qgsrasterfilewriter.h>
#include <qgsrasternuller.h>
#include <qgsapplication.h>
/** \ingroup UnitTests
* This is a unit test for the QgsRasterFileWriter class.
*/
class TestQgsRasterFileWriter: public QObject
{
Q_OBJECT;
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init() {};// will be called before each testfunction is executed.
void cleanup() {};// will be called after every testfunction.
void writeTest();
private:
bool writeTest( QString rasterName );
void log ( QString msg );
void logError ( QString msg );
QString mTestDataDir;
QString mReport;
};
//runs before all tests
void TestQgsRasterFileWriter::initTestCase()
{
// init QGIS's paths - true means that all path will be inited from prefix
QgsApplication::init();
QgsApplication::initQgis();
// disable any PAM stuff to make sure stats are consistent
CPLSetConfigOption( "GDAL_PAM_ENABLED", "NO" );
QString mySettings = QgsApplication::showSettings();
mySettings = mySettings.replace( "\n", "<br />" );
//create some objects that will be used in all tests...
//create a raster layer that will be used in all tests...
mTestDataDir = QString( TEST_DATA_DIR ) + QDir::separator(); //defined in CmakeLists.txt
mReport += "<h1>Raster File Writer Tests</h1>\n";
mReport += "<p>" + mySettings + "</p>";
}
//runs after all tests
void TestQgsRasterFileWriter::cleanupTestCase()
{
QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html";
QFile myFile( myReportFile );
if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
{
QTextStream myQTextStream( &myFile );
myQTextStream << mReport;
myFile.close();
}
}
void TestQgsRasterFileWriter::writeTest()
{
QDir dir( mTestDataDir + "/raster" );
QStringList filters;
filters << "*.tif";
QStringList rasterNames = dir.entryList( filters, QDir::Files );
bool allOK = true;
foreach ( QString rasterName, rasterNames )
{
bool ok = writeTest( "raster/" + rasterName );
if ( !ok ) allOK = false;
}
QVERIFY( allOK );
}
bool TestQgsRasterFileWriter::writeTest( QString theRasterName )
{
mReport += "<h2>" + theRasterName + "</h2>\n";
QString myFileName = mTestDataDir + "/" + theRasterName;
qDebug() << myFileName;
QFileInfo myRasterFileInfo( myFileName );
QgsRasterLayer * mpRasterLayer = new QgsRasterLayer( myRasterFileInfo.filePath(),
myRasterFileInfo.completeBaseName() );
qDebug() << theRasterName << " metadata: " << mpRasterLayer->dataProvider()->metadata();
if ( !mpRasterLayer->isValid() ) return false;
// Open provider only (avoid layer)?
QgsRasterDataProvider * provider = mpRasterLayer->dataProvider();
// I don't see any method to get only a name without opening file
QTemporaryFile tmpFile;
tmpFile.open(); // fileName is no avialable until open
QString tmpName = tmpFile.fileName();
tmpFile.close();
// do not remove when class is destroyd so that we can read the file and see difference
tmpFile.setAutoRemove ( false );
qDebug() << "temporary output file: " << tmpName;
mReport += "temporary output file: " + tmpName + "<br>";
QgsRasterFileWriter fileWriter( tmpName );
QgsRasterPipe* pipe = new QgsRasterPipe();
if ( !pipe->set( provider->clone() ) )
{
logError ( "Cannot set pipe provider" );
return false;
}
qDebug() << "provider set";
// Nuller currently is not really used
QgsRasterNuller *nuller = new QgsRasterNuller();
//nuller->setNoData( ... );
if ( !pipe->insert( 1, nuller ) )
{
logError( "Cannot set pipe nuller" );
return false;
}
qDebug() << "nuller set";
// Reprojection not really done
QgsRasterProjector *projector = new QgsRasterProjector;
projector->setCRS( provider->crs(), provider->crs() );
if ( !pipe->insert( 2, projector ) )
{
logError( "Cannot set pipe projector" );
return false;
}
qDebug() << "projector set";
fileWriter.writeRaster( pipe, provider->xSize() + 1, provider->ySize(), provider->extent(), provider->crs() );
delete pipe;
QgsRasterChecker checker;
bool ok = checker.runTest( "gdal", tmpName, "gdal", myRasterFileInfo.filePath() );
mReport += checker.report();
// All OK, we can delete the file
tmpFile.setAutoRemove ( ok );
return true;
}
void TestQgsRasterFileWriter::log ( QString msg )
{
mReport += msg + "<br>";
}
void TestQgsRasterFileWriter::logError ( QString msg )
{
mReport += "Error:<font color='red'>" + msg + "</font><br>";
qDebug() << msg;
}
QTEST_MAIN( TestQgsRasterFileWriter )
#include "moc_testqgsrasterfilewriter.cxx"

View File

@ -24,6 +24,7 @@
#include <qgsdatasourceuri.h>
#include <qgsrasterlayer.h>
#include <qgsrasterdataprovider.h>
#include <qgsrasterchecker.h>
#include <qgsproviderregistry.h>
#include <qgsapplication.h>
@ -44,15 +45,6 @@ class TestQgsWcsProvider: public QObject
void read();
private:
bool read( QString theIdentifier, QString theWcsUri, QString theFilePath, QString & theReport );
// Log error in html
void error( QString theMessage, QString &theReport );
// compare values and add table row in html report, set ok to false if not equal
QString compareHead();
bool compare( double wcsVal, double gdalVal, double theTolerance );
void compare( QString theParamName, int wcsVal, int gdalVal, QString &theReport, bool &theOk );
void compare( QString theParamName, double wcsVal, double gdalVal, QString &theReport, bool &theOk, double theTolerance = 0 );
void compareRow( QString theParamName, QString wcsVal, QString gdalVal, QString &theReport, bool theOk, QString theDifference = "", QString theTolerance = "" );
double tolerance( double val, int places = 6 );
QString mTestDataDir;
QString mReport;
QString mUrl;
@ -68,7 +60,8 @@ void TestQgsWcsProvider::initTestCase()
mySettings = mySettings.replace( "\n", "<br />" );
mReport += "<h1>WCS provider tests</h1>\n";
mReport += "<p>" + mySettings + "</p>";
// Style is now inlined by QgsRasterChecker
#if 0
mReport += "<style>";
mReport += ".tab { border-spacing: 0px; border-width: 1px 1px 0 0; border-style: solid; }";
mReport += ".cell { border-width: 0 0 1px 1px; border-style: solid; font-size: smaller; text-align: center}";
@ -76,8 +69,7 @@ void TestQgsWcsProvider::initTestCase()
mReport += ".err { background: #ff0000; }";
mReport += ".errmsg { color: #ff0000; }";
mReport += "</style>";
#endif
//create some objects that will be used in all tests...
//create a raster layer that will be used in all tests...
mTestDataDir = QString( TEST_DATA_DIR ) + "/raster";
@ -89,10 +81,9 @@ void TestQgsWcsProvider::initTestCase()
//runs after all tests
void TestQgsWcsProvider::cleanupTestCase()
{
QString myReportFile = QDir::tempPath() + "/qgiswcstest.html";
QString myReportFile = QDir::tempPath() + "/qgistest.html";
QFile myFile( myReportFile );
//if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
if ( myFile.open( QIODevice::WriteOnly ) )
if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
{
QTextStream myQTextStream( &myFile );
myQTextStream << mReport;
@ -141,198 +132,13 @@ void TestQgsWcsProvider::read( )
bool TestQgsWcsProvider::read( QString theIdentifier, QString theWcsUri, QString theFilePath, QString & theReport )
{
bool ok = true;
theReport += QString( "<h2>Identifier (coverage): %1</h2>" ).arg( theIdentifier );
QgsRasterDataProvider* wcsProvider = QgsRasterLayer::loadProvider( "wcs", theWcsUri );
if ( !wcsProvider || !wcsProvider->isValid() )
{
error( QString( "Cannot load WCS provider with URI: %1" ).arg( QString( theWcsUri ) ), theReport );
ok = false;
}
QgsRasterChecker checker;
bool ok = checker.runTest( "wcs", theWcsUri, "gdal", theFilePath );
QgsRasterDataProvider* gdalProvider = QgsRasterLayer::loadProvider( "gdal", theFilePath );
if ( !gdalProvider || !gdalProvider->isValid() )
{
error( QString( "Cannot load GDAL provider with URI: %1" ).arg( theFilePath ), theReport );
ok = false;
}
if ( !ok ) return false;
theReport += QString( "WCS URI: %1<br>" ).arg( theWcsUri.replace( "&", "&amp;" ) );
theReport += QString( "GDAL URI: %1<br>" ).arg( theFilePath );
theReport += "<br>";
theReport += "<table class='tab'>";
theReport += compareHead();
compare( "Band count", wcsProvider->bandCount(), gdalProvider->bandCount(), theReport, ok );
compare( "Width", wcsProvider->xSize(), gdalProvider->xSize(), theReport, ok );
compare( "Height", wcsProvider->ySize(), gdalProvider->ySize(), theReport, ok );
compareRow( "Extent", wcsProvider->extent().toString(), gdalProvider->extent().toString(), theReport, wcsProvider->extent() == gdalProvider->extent() );
if ( wcsProvider->extent() != gdalProvider->extent() ) ok = false;
if ( !ok ) return false;
compare( "No data (NULL) value", wcsProvider->noDataValue(), gdalProvider->noDataValue(), theReport, ok );
theReport += "</table>";
bool allOk = true;
for ( int band = 1; band <= gdalProvider->bandCount(); band++ )
{
bool bandOk = true;
theReport += QString( "<h3>Band %1</h3>" ).arg( band );
theReport += "<table class='tab'>";
theReport += compareHead();
// Data types may differ (?)
bool typesOk = true;
compare( "Source data type", wcsProvider->srcDataType( band ), gdalProvider->srcDataType( band ), theReport, typesOk );
compare( "Data type", wcsProvider->dataType( band ), gdalProvider->dataType( band ), theReport, typesOk ) ;
bool statsOk = true;
QgsRasterBandStats wcsStats = wcsProvider->bandStatistics( band );
QgsRasterBandStats gdalStats = gdalProvider->bandStatistics( band );
// Min/max may 'slightly' differ, for big numbers however, the difference may
// be quite big, for example for Float32 with max -3.332e+38, the difference is 1.47338e+24
double tol = tolerance( gdalStats.minimumValue );
compare( "Minimum value", wcsStats.minimumValue, gdalStats.minimumValue, theReport, statsOk, tol );
tol = tolerance( gdalStats.maximumValue );
compare( "Maximum value", wcsStats.maximumValue, gdalStats.maximumValue, theReport, statsOk, tol );
// TODO: enable once fixed (WCS excludes nulls but GDAL does not)
//compare( "Cells count", wcsStats.elementCount, gdalStats.elementCount, theReport, statsOk );
tol = tolerance( gdalStats.mean );
compare( "Mean", wcsStats.mean, gdalStats.mean, theReport, statsOk, tol );
// stdDev usually differ significantly
tol = tolerance( gdalStats.stdDev, 1 );
compare( "Standard deviation", wcsStats.stdDev, gdalStats.stdDev, theReport, statsOk, tol );
theReport += "</table>";
theReport += "<br>";
if ( !bandOk )
{
allOk = false;
continue;
}
if ( !statsOk || !typesOk )
{
allOk = false;
// create values table anyway so that values are available
}
theReport += "<table><tr>";
theReport += "<td>Data comparison</td>";
theReport += "<td class='cell ok' style='border: 1px solid'>correct&nbsp;value</td>";
theReport += "<td></td>";
theReport += "<td class='cell err' style='border: 1px solid'>wrong&nbsp;value<br>expected value</td></tr>";
theReport += "</tr></table>";
theReport += "<br>";
int width = gdalProvider->xSize();
int height = gdalProvider->ySize();
int blockSize = width * height * gdalProvider->typeSize( gdalProvider->dataType( band ) ) ;
void * gdalData = malloc( blockSize );
void * wcsData = malloc( blockSize );
gdalProvider->readBlock( band, gdalProvider->extent(), width, height, gdalData );
wcsProvider->readBlock( band, gdalProvider->extent(), width, height, wcsData );
// compare data values
QString htmlTable = "<table class='tab'>";
for ( int row = 0; row < height; row ++ )
{
htmlTable += "<tr>";
for ( int col = 0; col < width; col ++ )
{
bool cellOk = true;
double wcsVal = wcsProvider->readValue( wcsData, wcsProvider->dataType( band ), row * width + col );
double gdalVal = gdalProvider->readValue( gdalData, gdalProvider->dataType( band ), row * width + col );
QString valStr;
if ( compare( wcsVal, gdalVal, 0 ) )
{
valStr = QString( "%1" ).arg( wcsVal );
}
else
{
cellOk = false;
allOk = false;
valStr = QString( "%1<br>%2" ).arg( wcsVal ).arg( gdalVal );
}
htmlTable += QString( "<td class='cell %1'>%2</td>" ).arg( cellOk ? "ok" : "err" ).arg( valStr );
}
htmlTable += "</tr>";
}
htmlTable += "</table>";
theReport += htmlTable;
free( gdalData );
free( wcsData );
}
delete wcsProvider;
delete gdalProvider;
return allOk;
}
void TestQgsWcsProvider::error( QString theMessage, QString &theReport )
{
theReport += "<font class='errmsg'>Error: ";
theReport += theMessage;
theReport += "</font>";
}
double TestQgsWcsProvider::tolerance( double val, int places )
{
// float precision is about 7 decimal digits, double about 16
// default places = 6
return 1. * qPow( 10, qRound( log10( qAbs( val ) ) - places ) );
}
QString TestQgsWcsProvider::compareHead()
{
return "<tr><th class='cell'>Param name</th><th class='cell'>WCS (tested) value</th><th class='cell'>GDAL (expected) value</th><th class='cell'>Difference</th><th class='cell'>Tolerance</th></tr>";
}
void TestQgsWcsProvider::compare( QString theParamName, int wcsVal, int gdalVal, QString &theReport, bool &theOk )
{
bool ok = wcsVal == gdalVal;
compareRow( theParamName, QString::number( wcsVal ), QString::number( gdalVal ), theReport, ok, QString::number( wcsVal - gdalVal ) );
if ( !ok ) theOk = false;
}
bool TestQgsWcsProvider::compare( double wcsVal, double gdalVal, double theTolerance )
{
// values may be nan
return ( qIsNaN( wcsVal ) && qIsNaN( gdalVal ) ) || ( qAbs( wcsVal - gdalVal ) <= theTolerance );
}
void TestQgsWcsProvider::compare( QString theParamName, double wcsVal, double gdalVal, QString &theReport, bool &theOk, double theTolerance )
{
bool ok = compare( wcsVal, gdalVal, theTolerance );
compareRow( theParamName, QString::number( wcsVal ), QString::number( gdalVal ), theReport, ok, QString::number( wcsVal - gdalVal ), QString::number( theTolerance ) );
if ( !ok ) theOk = false;
}
void TestQgsWcsProvider::compareRow( QString theParamName, QString wcsVal, QString gdalVal, QString &theReport, bool theOk, QString theDifference, QString theTolerance )
{
theReport += "<tr>";
theReport += QString( "<td class='cell'>%1</td><td class='cell %2'>%3</td><td class='cell'>%4</td>" ).arg( theParamName ).arg( theOk ? "ok" : "err" ).arg( wcsVal ).arg( gdalVal );
theReport += QString( "<td class='cell'>%1</td>" ).arg( theDifference );
theReport += QString( "<td class='cell'>%1</td>" ).arg( theTolerance );
theReport += "</tr>";
theReport += checker.report();
return ok;
}
QTEST_MAIN( TestQgsWcsProvider )