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