QGIS API Documentation  2.99.0-Master (7d4f81d)
qgsrasterchecker.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrasterchecker.cpp
3  --------------------------------------
4  Date : 5 Sep 2012
5  Copyright : (C) 2012 by Radim Blazek
6  Email : radim dot blazek at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsproviderregistry.h"
17 #include "qgsrasterchecker.h"
18 #include "qgsrasterdataprovider.h"
19 #include "qgsrasterlayer.h"
20 
21 #include <qmath.h>
22 #include <QColor>
23 #include <QPainter>
24 #include <QImage>
25 #include <QTime>
26 #include <QCryptographicHash>
27 #include <QByteArray>
28 #include <QDebug>
29 #include <QBuffer>
30 
32  : mReport( QLatin1String( "" ) )
33 {
34  mTabStyle = QStringLiteral( "border-spacing: 0px; border-width: 1px 1px 0 0; border-style: solid;" );
35  mCellStyle = QStringLiteral( "border-width: 0 0 1px 1px; border-style: solid; font-size: smaller; text-align: center;" );
36  mOkStyle = QStringLiteral( "background: #00ff00;" );
37  mErrStyle = QStringLiteral( "background: #ff0000;" );
38  mErrMsgStyle = QStringLiteral( "color: #ff0000;" );
39 }
40 
41 bool QgsRasterChecker::runTest( const QString &verifiedKey, QString verifiedUri,
42  const QString &expectedKey, QString expectedUri )
43 {
44  bool ok = true;
45  mReport += QLatin1String( "\n\n" );
46 
47  //QgsRasterDataProvider* verifiedProvider = QgsRasterLayer::loadProvider( verifiedKey, verifiedUri );
48  QgsRasterDataProvider *verifiedProvider = dynamic_cast< QgsRasterDataProvider * >( QgsProviderRegistry::instance()->createProvider( verifiedKey, verifiedUri ) );
49  if ( !verifiedProvider || !verifiedProvider->isValid() )
50  {
51  error( QStringLiteral( "Cannot load provider %1 with URI: %2" ).arg( verifiedKey, verifiedUri ), mReport );
52  ok = false;
53  }
54 
55  //QgsRasterDataProvider* expectedProvider = QgsRasterLayer::loadProvider( expectedKey, expectedUri );
56  QgsRasterDataProvider *expectedProvider = dynamic_cast< QgsRasterDataProvider * >( QgsProviderRegistry::instance()->createProvider( expectedKey, expectedUri ) );
57  if ( !expectedProvider || !expectedProvider->isValid() )
58  {
59  error( QStringLiteral( "Cannot load provider %1 with URI: %2" ).arg( expectedKey, expectedUri ), mReport );
60  ok = false;
61  }
62 
63  if ( !ok ) return false;
64 
65  mReport += QStringLiteral( "Verified URI: %1<br>" ).arg( verifiedUri.replace( '&', QLatin1String( "&amp;" ) ) );
66  mReport += QStringLiteral( "Expected URI: %1<br>" ).arg( expectedUri.replace( '&', QLatin1String( "&amp;" ) ) );
67 
68  mReport += QLatin1String( "<br>" );
69  mReport += QStringLiteral( "<table style='%1'>\n" ).arg( mTabStyle );
70  mReport += compareHead();
71 
72  compare( QStringLiteral( "Band count" ), verifiedProvider->bandCount(), expectedProvider->bandCount(), mReport, ok );
73 
74  compare( QStringLiteral( "Width" ), verifiedProvider->xSize(), expectedProvider->xSize(), mReport, ok );
75  compare( QStringLiteral( "Height" ), verifiedProvider->ySize(), expectedProvider->ySize(), mReport, ok );
76 
77  compareRow( QStringLiteral( "Extent" ), verifiedProvider->extent().toString(), expectedProvider->extent().toString(), mReport, verifiedProvider->extent() == expectedProvider->extent() );
78 
79  if ( verifiedProvider->extent() != expectedProvider->extent() ) ok = false;
80 
81 
82  mReport += QLatin1String( "</table>\n" );
83 
84  if ( !ok ) return false;
85 
86  bool allOk = true;
87  for ( int band = 1; band <= expectedProvider->bandCount(); band++ )
88  {
89  mReport += QStringLiteral( "<h3>Band %1</h3>\n" ).arg( band );
90  mReport += QStringLiteral( "<table style='%1'>\n" ).arg( mTabStyle );
91  mReport += compareHead();
92 
93  // Data types may differ (?)
94  bool typesOk = true;
95  compare( QStringLiteral( "Source data type" ), verifiedProvider->sourceDataType( band ), expectedProvider->sourceDataType( band ), mReport, typesOk );
96  compare( QStringLiteral( "Data type" ), verifiedProvider->dataType( band ), expectedProvider->dataType( band ), mReport, typesOk );
97 
98  // Check nodata
99  bool noDataOk = true;
100  compare( QStringLiteral( "No data (NULL) value existence flag" ), verifiedProvider->sourceHasNoDataValue( band ), expectedProvider->sourceHasNoDataValue( band ), mReport, noDataOk );
101  if ( verifiedProvider->sourceHasNoDataValue( band ) && expectedProvider->sourceHasNoDataValue( band ) )
102  {
103  compare( QStringLiteral( "No data (NULL) value" ), verifiedProvider->sourceNoDataValue( band ), expectedProvider->sourceNoDataValue( band ), mReport, noDataOk );
104  }
105 
106  bool statsOk = true;
107  QgsRasterBandStats verifiedStats = verifiedProvider->bandStatistics( band );
108  QgsRasterBandStats expectedStats = expectedProvider->bandStatistics( band );
109 
110  // Min/max may 'slightly' differ, for big numbers however, the difference may
111  // be quite big, for example for Float32 with max -3.332e+38, the difference is 1.47338e+24
112  double tol = tolerance( expectedStats.minimumValue );
113  compare( QStringLiteral( "Minimum value" ), verifiedStats.minimumValue, expectedStats.minimumValue, mReport, statsOk, tol );
114  tol = tolerance( expectedStats.maximumValue );
115  compare( QStringLiteral( "Maximum value" ), verifiedStats.maximumValue, expectedStats.maximumValue, mReport, statsOk, tol );
116 
117  // TODO: enable once fixed (WCS excludes nulls but GDAL does not)
118  //compare( "Cells count", verifiedStats.elementCount, expectedStats.elementCount, mReport, statsOk );
119 
120  tol = tolerance( expectedStats.mean );
121  compare( QStringLiteral( "Mean" ), verifiedStats.mean, expectedStats.mean, mReport, statsOk, tol );
122 
123  // stdDev usually differ significantly
124  tol = tolerance( expectedStats.stdDev, 1 );
125  compare( QStringLiteral( "Standard deviation" ), verifiedStats.stdDev, expectedStats.stdDev, mReport, statsOk, tol );
126 
127  mReport += QLatin1String( "</table>" );
128  mReport += QLatin1String( "<br>" );
129 
130  if ( !statsOk || !typesOk || !noDataOk )
131  {
132  allOk = false;
133  // create values table anyway so that values are available
134  }
135 
136  mReport += QLatin1String( "<table><tr>" );
137  mReport += QLatin1String( "<td>Data comparison</td>" );
138  mReport += QStringLiteral( "<td style='%1 %2 border: 1px solid'>correct&nbsp;value</td>" ).arg( mCellStyle, mOkStyle );
139  mReport += QLatin1String( "<td></td>" );
140  mReport += QStringLiteral( "<td style='%1 %2 border: 1px solid'>wrong&nbsp;value<br>expected value</td></tr>" ).arg( mCellStyle, mErrStyle );
141  mReport += QLatin1String( "</tr></table>" );
142  mReport += QLatin1String( "<br>" );
143 
144  int width = expectedProvider->xSize();
145  int height = expectedProvider->ySize();
146  QgsRasterBlock *expectedBlock = expectedProvider->block( band, expectedProvider->extent(), width, height );
147  QgsRasterBlock *verifiedBlock = verifiedProvider->block( band, expectedProvider->extent(), width, height );
148 
149  if ( !expectedBlock || !expectedBlock->isValid() ||
150  !verifiedBlock || !verifiedBlock->isValid() )
151  {
152  allOk = false;
153  mReport += QLatin1String( "cannot read raster block" );
154  continue;
155  }
156 
157  // compare data values
158  QString htmlTable = QStringLiteral( "<table style='%1'>" ).arg( mTabStyle );
159  for ( int row = 0; row < height; row ++ )
160  {
161  htmlTable += QLatin1String( "<tr>" );
162  for ( int col = 0; col < width; col ++ )
163  {
164  bool cellOk = true;
165  double verifiedVal = verifiedBlock->value( row, col );
166  double expectedVal = expectedBlock->value( row, col );
167 
168  QString valStr;
169  if ( compare( verifiedVal, expectedVal, 0 ) )
170  {
171  valStr = QStringLiteral( "%1" ).arg( verifiedVal );
172  }
173  else
174  {
175  cellOk = false;
176  allOk = false;
177  valStr = QStringLiteral( "%1<br>%2" ).arg( verifiedVal ).arg( expectedVal );
178  }
179  htmlTable += QStringLiteral( "<td style='%1 %2'>%3</td>" ).arg( mCellStyle, cellOk ? mOkStyle : mErrStyle, valStr );
180  }
181  htmlTable += QLatin1String( "</tr>" );
182  }
183  htmlTable += QLatin1String( "</table>" );
184 
185  mReport += htmlTable;
186 
187  delete expectedBlock;
188  delete verifiedBlock;
189  }
190  delete verifiedProvider;
191  delete expectedProvider;
192  return allOk;
193 }
194 
195 void QgsRasterChecker::error( const QString &message, QString &report )
196 {
197  report += QStringLiteral( "<font style='%1'>Error: " ).arg( mErrMsgStyle );
198  report += message;
199  report += QLatin1String( "</font>" );
200 }
201 
202 double QgsRasterChecker::tolerance( double val, int places )
203 {
204  // float precision is about 7 decimal digits, double about 16
205  // default places = 6
206  return 1. * qPow( 10, qRound( log10( qAbs( val ) ) - places ) );
207 }
208 
209 QString QgsRasterChecker::compareHead()
210 {
211  QString html;
212  html += QStringLiteral( "<tr><th style='%1'>Param name</th><th style='%1'>Verified value</th><th style='%1'>Expected value</th><th style='%1'>Difference</th><th style='%1'>Tolerance</th></tr>" ).arg( mCellStyle );
213  return html;
214 }
215 
216 void QgsRasterChecker::compare( const QString &paramName, int verifiedVal, int expectedVal, QString &report, bool &ok )
217 {
218  bool isEqual = verifiedVal == expectedVal;
219  compareRow( paramName, QString::number( verifiedVal ), QString::number( expectedVal ), report, isEqual, QString::number( verifiedVal - expectedVal ) );
220  if ( !isEqual )
221  ok = false;
222 }
223 
224 bool QgsRasterChecker::compare( double verifiedVal, double expectedVal, double tolerance )
225 {
226  // values may be nan
227  return ( qIsNaN( verifiedVal ) && qIsNaN( expectedVal ) ) || ( qAbs( verifiedVal - expectedVal ) <= tolerance );
228 }
229 
230 void QgsRasterChecker::compare( const QString &paramName, double verifiedVal, double expectedVal, QString &report, bool &ok, double tolerance )
231 {
232  bool isNearEqual = compare( verifiedVal, expectedVal, tolerance );
233  compareRow( paramName, QString::number( verifiedVal ), QString::number( expectedVal ), report, isNearEqual, QString::number( verifiedVal - expectedVal ), QString::number( tolerance ) );
234  if ( !isNearEqual )
235  ok = false;
236 }
237 
238 void QgsRasterChecker::compareRow( const QString &paramName, const QString &verifiedVal, const QString &expectedVal, QString &report, bool ok, const QString &difference, const QString &tolerance )
239 {
240  report += QLatin1String( "<tr>\n" );
241  report += QStringLiteral( "<td style='%1'>%2</td><td style='%1 %3'>%4</td><td style='%1'>%5</td>\n" ).arg( mCellStyle, paramName, ok ? mOkStyle : mErrStyle, verifiedVal, expectedVal );
242  report += QStringLiteral( "<td style='%1'>%2</td>\n" ).arg( mCellStyle, difference );
243  report += QStringLiteral( "<td style='%1'>%2</td>\n" ).arg( mCellStyle, tolerance );
244  report += QLatin1String( "</tr>" );
245 }
virtual QgsRectangle extent() const override=0
Returns the extent of the layer.
virtual int bandCount() const =0
Get number of bands.
bool isValid() const
Returns true if the block is valid (correctly filled with data).
double maximumValue
The maximum cell value in the raster band.
QgsDataProvider * createProvider(const QString &providerKey, const QString &dataSource)
Creates a new instance of a provider.
virtual int ySize() const
virtual double sourceNoDataValue(int bandNo) const
Value representing no data value.
virtual Qgis::DataType sourceDataType(int bandNo) const override=0
Returns source data type for the band specified by number, source data type may be shorter than dataT...
double stdDev
The standard deviation of the cell values.
The RasterBandStats struct is a container for statistics about a single raster band.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
double mean
The mean cell value for the band. NO_DATA values are excluded.
virtual Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
Raster data container.
virtual bool isValid() const =0
Returns true if this is a valid layer.
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
virtual bool sourceHasNoDataValue(int bandNo) const
Return true if source band has no data value.
bool runTest(const QString &verifiedKey, QString verifiedUri, const QString &expectedKey, QString expectedUri)
Test using renderer to generate the image to be compared.
virtual QgsRasterBandStats bandStatistics(int bandNo, int stats=QgsRasterBandStats::All, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Get band statistics.
virtual QgsRasterBlock * block(int bandNo, const QgsRectangle &boundingBox, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
double value(int row, int column) const
Read a single value if type of block is numeric.
double minimumValue
The minimum cell value in the raster band.
virtual int xSize() const
Get raster size.
Base class for raster data providers.