QGIS API Documentation  2.99.0-Master (19b062c)
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 <QColor>
22 #include <QPainter>
23 #include <QImage>
24 #include <QTime>
25 #include <QCryptographicHash>
26 #include <QByteArray>
27 #include <QDebug>
28 #include <QBuffer>
29 
31 {
32  mTabStyle = QStringLiteral( "border-spacing: 0px; border-width: 1px 1px 0 0; border-style: solid;" );
33  mCellStyle = QStringLiteral( "border-width: 0 0 1px 1px; border-style: solid; font-size: smaller; text-align: center;" );
34  mOkStyle = QStringLiteral( "background: #00ff00;" );
35  mErrStyle = QStringLiteral( "background: #ff0000;" );
36  mErrMsgStyle = QStringLiteral( "color: #ff0000;" );
37 }
38 
39 bool QgsRasterChecker::runTest( const QString &verifiedKey, QString verifiedUri,
40  const QString &expectedKey, QString expectedUri )
41 {
42  bool ok = true;
43  mReport += QLatin1String( "\n\n" );
44 
45  //QgsRasterDataProvider* verifiedProvider = QgsRasterLayer::loadProvider( verifiedKey, verifiedUri );
46  QgsRasterDataProvider *verifiedProvider = dynamic_cast< QgsRasterDataProvider * >( QgsProviderRegistry::instance()->createProvider( verifiedKey, verifiedUri ) );
47  if ( !verifiedProvider || !verifiedProvider->isValid() )
48  {
49  error( QStringLiteral( "Cannot load provider %1 with URI: %2" ).arg( verifiedKey, verifiedUri ), mReport );
50  ok = false;
51  }
52 
53  //QgsRasterDataProvider* expectedProvider = QgsRasterLayer::loadProvider( expectedKey, expectedUri );
54  QgsRasterDataProvider *expectedProvider = dynamic_cast< QgsRasterDataProvider * >( QgsProviderRegistry::instance()->createProvider( expectedKey, expectedUri ) );
55  if ( !expectedProvider || !expectedProvider->isValid() )
56  {
57  error( QStringLiteral( "Cannot load provider %1 with URI: %2" ).arg( expectedKey, expectedUri ), mReport );
58  ok = false;
59  }
60 
61  if ( !ok ) return false;
62 
63  mReport += QStringLiteral( "Verified URI: %1<br>" ).arg( verifiedUri.replace( '&', QLatin1String( "&amp;" ) ) );
64  mReport += QStringLiteral( "Expected URI: %1<br>" ).arg( expectedUri.replace( '&', QLatin1String( "&amp;" ) ) );
65 
66  mReport += QLatin1String( "<br>" );
67  mReport += QStringLiteral( "<table style='%1'>\n" ).arg( mTabStyle );
68  mReport += compareHead();
69 
70  compare( QStringLiteral( "Band count" ), verifiedProvider->bandCount(), expectedProvider->bandCount(), mReport, ok );
71 
72  compare( QStringLiteral( "Width" ), verifiedProvider->xSize(), expectedProvider->xSize(), mReport, ok );
73  compare( QStringLiteral( "Height" ), verifiedProvider->ySize(), expectedProvider->ySize(), mReport, ok );
74 
75  compareRow( QStringLiteral( "Extent" ), verifiedProvider->extent().toString(), expectedProvider->extent().toString(), mReport, verifiedProvider->extent() == expectedProvider->extent() );
76 
77  if ( verifiedProvider->extent() != expectedProvider->extent() ) ok = false;
78 
79 
80  mReport += QLatin1String( "</table>\n" );
81 
82  if ( !ok ) return false;
83 
84  bool allOk = true;
85  for ( int band = 1; band <= expectedProvider->bandCount(); band++ )
86  {
87  mReport += QStringLiteral( "<h3>Band %1</h3>\n" ).arg( band );
88  mReport += QStringLiteral( "<table style='%1'>\n" ).arg( mTabStyle );
89  mReport += compareHead();
90 
91  // Data types may differ (?)
92  bool typesOk = true;
93  compare( QStringLiteral( "Source data type" ), verifiedProvider->sourceDataType( band ), expectedProvider->sourceDataType( band ), mReport, typesOk );
94  compare( QStringLiteral( "Data type" ), verifiedProvider->dataType( band ), expectedProvider->dataType( band ), mReport, typesOk );
95 
96  // Check nodata
97  bool noDataOk = true;
98  compare( QStringLiteral( "No data (NULL) value existence flag" ), verifiedProvider->sourceHasNoDataValue( band ), expectedProvider->sourceHasNoDataValue( band ), mReport, noDataOk );
99  if ( verifiedProvider->sourceHasNoDataValue( band ) && expectedProvider->sourceHasNoDataValue( band ) )
100  {
101  compare( QStringLiteral( "No data (NULL) value" ), verifiedProvider->sourceNoDataValue( band ), expectedProvider->sourceNoDataValue( band ), mReport, noDataOk );
102  }
103 
104  bool statsOk = true;
105  QgsRasterBandStats verifiedStats = verifiedProvider->bandStatistics( band );
106  QgsRasterBandStats expectedStats = expectedProvider->bandStatistics( band );
107 
108  // Min/max may 'slightly' differ, for big numbers however, the difference may
109  // be quite big, for example for Float32 with max -3.332e+38, the difference is 1.47338e+24
110  double tol = tolerance( expectedStats.minimumValue );
111  compare( QStringLiteral( "Minimum value" ), verifiedStats.minimumValue, expectedStats.minimumValue, mReport, statsOk, tol );
112  tol = tolerance( expectedStats.maximumValue );
113  compare( QStringLiteral( "Maximum value" ), verifiedStats.maximumValue, expectedStats.maximumValue, mReport, statsOk, tol );
114 
115  // TODO: enable once fixed (WCS excludes nulls but GDAL does not)
116  //compare( "Cells count", verifiedStats.elementCount, expectedStats.elementCount, mReport, statsOk );
117 
118  tol = tolerance( expectedStats.mean );
119  compare( QStringLiteral( "Mean" ), verifiedStats.mean, expectedStats.mean, mReport, statsOk, tol );
120 
121  // stdDev usually differ significantly
122  tol = tolerance( expectedStats.stdDev, 1 );
123  compare( QStringLiteral( "Standard deviation" ), verifiedStats.stdDev, expectedStats.stdDev, mReport, statsOk, tol );
124 
125  mReport += QLatin1String( "</table>" );
126  mReport += QLatin1String( "<br>" );
127 
128  if ( !statsOk || !typesOk || !noDataOk )
129  {
130  allOk = false;
131  // create values table anyway so that values are available
132  }
133 
134  mReport += QLatin1String( "<table><tr>" );
135  mReport += QLatin1String( "<td>Data comparison</td>" );
136  mReport += QStringLiteral( "<td style='%1 %2 border: 1px solid'>correct&nbsp;value</td>" ).arg( mCellStyle, mOkStyle );
137  mReport += QLatin1String( "<td></td>" );
138  mReport += QStringLiteral( "<td style='%1 %2 border: 1px solid'>wrong&nbsp;value<br>expected value</td></tr>" ).arg( mCellStyle, mErrStyle );
139  mReport += QLatin1String( "</tr></table>" );
140  mReport += QLatin1String( "<br>" );
141 
142  int width = expectedProvider->xSize();
143  int height = expectedProvider->ySize();
144  QgsRasterBlock *expectedBlock = expectedProvider->block( band, expectedProvider->extent(), width, height );
145  QgsRasterBlock *verifiedBlock = verifiedProvider->block( band, expectedProvider->extent(), width, height );
146 
147  if ( !expectedBlock || !expectedBlock->isValid() ||
148  !verifiedBlock || !verifiedBlock->isValid() )
149  {
150  allOk = false;
151  mReport += QLatin1String( "cannot read raster block" );
152  continue;
153  }
154 
155  // compare data values
156  QString htmlTable = QStringLiteral( "<table style='%1'>" ).arg( mTabStyle );
157  for ( int row = 0; row < height; row ++ )
158  {
159  htmlTable += QLatin1String( "<tr>" );
160  for ( int col = 0; col < width; col ++ )
161  {
162  bool cellOk = true;
163  double verifiedVal = verifiedBlock->value( row, col );
164  double expectedVal = expectedBlock->value( row, col );
165 
166  QString valStr;
167  if ( compare( verifiedVal, expectedVal, 0 ) )
168  {
169  valStr = QStringLiteral( "%1" ).arg( verifiedVal );
170  }
171  else
172  {
173  cellOk = false;
174  allOk = false;
175  valStr = QStringLiteral( "%1<br>%2" ).arg( verifiedVal ).arg( expectedVal );
176  }
177  htmlTable += QStringLiteral( "<td style='%1 %2'>%3</td>" ).arg( mCellStyle, cellOk ? mOkStyle : mErrStyle, valStr );
178  }
179  htmlTable += QLatin1String( "</tr>" );
180  }
181  htmlTable += QLatin1String( "</table>" );
182 
183  mReport += htmlTable;
184 
185  delete expectedBlock;
186  delete verifiedBlock;
187  }
188  delete verifiedProvider;
189  delete expectedProvider;
190  return allOk;
191 }
192 
193 void QgsRasterChecker::error( const QString &message, QString &report )
194 {
195  report += QStringLiteral( "<font style='%1'>Error: " ).arg( mErrMsgStyle );
196  report += message;
197  report += QLatin1String( "</font>" );
198 }
199 
200 double QgsRasterChecker::tolerance( double val, int places )
201 {
202  // float precision is about 7 decimal digits, double about 16
203  // default places = 6
204  return 1. * std::pow( 10, std::round( std::log10( std::fabs( val ) ) - places ) );
205 }
206 
207 QString QgsRasterChecker::compareHead()
208 {
209  QString html;
210  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 );
211  return html;
212 }
213 
214 void QgsRasterChecker::compare( const QString &paramName, int verifiedVal, int expectedVal, QString &report, bool &ok )
215 {
216  bool isEqual = verifiedVal == expectedVal;
217  compareRow( paramName, QString::number( verifiedVal ), QString::number( expectedVal ), report, isEqual, QString::number( verifiedVal - expectedVal ) );
218  if ( !isEqual )
219  ok = false;
220 }
221 
222 bool QgsRasterChecker::compare( double verifiedVal, double expectedVal, double tolerance )
223 {
224  // values may be nan
225  return ( std::isnan( verifiedVal ) && std::isnan( expectedVal ) ) || ( std::fabs( verifiedVal - expectedVal ) <= tolerance );
226 }
227 
228 void QgsRasterChecker::compare( const QString &paramName, double verifiedVal, double expectedVal, QString &report, bool &ok, double tolerance )
229 {
230  bool isNearEqual = compare( verifiedVal, expectedVal, tolerance );
231  compareRow( paramName, QString::number( verifiedVal ), QString::number( expectedVal ), report, isNearEqual, QString::number( verifiedVal - expectedVal ), QString::number( tolerance ) );
232  if ( !isNearEqual )
233  ok = false;
234 }
235 
236 void QgsRasterChecker::compareRow( const QString &paramName, const QString &verifiedVal, const QString &expectedVal, QString &report, bool ok, const QString &difference, const QString &tolerance )
237 {
238  report += QLatin1String( "<tr>\n" );
239  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 );
240  report += QStringLiteral( "<td style='%1'>%2</td>\n" ).arg( mCellStyle, difference );
241  report += QStringLiteral( "<td style='%1'>%2</td>\n" ).arg( mCellStyle, tolerance );
242  report += QLatin1String( "</tr>" );
243 }
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.