QGIS API Documentation  3.6.0-Noosa (5873452)
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 );
47  QgsRasterDataProvider *verifiedProvider = dynamic_cast< QgsRasterDataProvider * >( QgsProviderRegistry::instance()->createProvider( verifiedKey, verifiedUri, options ) );
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, options ) );
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  std::unique_ptr< QgsRasterBlock > expectedBlock( expectedProvider->block( band, expectedProvider->extent(), width, height ) );
146  std::unique_ptr< 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 verifiedProvider;
187  delete expectedProvider;
188  return allOk;
189 }
190 
191 void QgsRasterChecker::error( const QString &message, QString &report )
192 {
193  report += QStringLiteral( "<font style='%1'>Error: " ).arg( mErrMsgStyle );
194  report += message;
195  report += QLatin1String( "</font>" );
196 }
197 
198 double QgsRasterChecker::tolerance( double val, int places )
199 {
200  // float precision is about 7 decimal digits, double about 16
201  // default places = 6
202  return 1. * std::pow( 10, std::round( std::log10( std::fabs( val ) ) - places ) );
203 }
204 
205 QString QgsRasterChecker::compareHead()
206 {
207  QString html;
208  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 );
209  return html;
210 }
211 
212 void QgsRasterChecker::compare( const QString &paramName, int verifiedVal, int expectedVal, QString &report, bool &ok )
213 {
214  bool isEqual = verifiedVal == expectedVal;
215  compareRow( paramName, QString::number( verifiedVal ), QString::number( expectedVal ), report, isEqual, QString::number( verifiedVal - expectedVal ) );
216  if ( !isEqual )
217  ok = false;
218 }
219 
220 bool QgsRasterChecker::compare( double verifiedVal, double expectedVal, double tolerance )
221 {
222  // values may be nan
223  return ( std::isnan( verifiedVal ) && std::isnan( expectedVal ) ) || ( std::fabs( verifiedVal - expectedVal ) <= tolerance );
224 }
225 
226 void QgsRasterChecker::compare( const QString &paramName, double verifiedVal, double expectedVal, QString &report, bool &ok, double tolerance )
227 {
228  bool isNearEqual = compare( verifiedVal, expectedVal, tolerance );
229  compareRow( paramName, QString::number( verifiedVal ), QString::number( expectedVal ), report, isNearEqual, QString::number( verifiedVal - expectedVal ), QString::number( tolerance ) );
230  if ( !isNearEqual )
231  ok = false;
232 }
233 
234 void QgsRasterChecker::compareRow( const QString &paramName, const QString &verifiedVal, const QString &expectedVal, QString &report, bool ok, const QString &difference, const QString &tolerance )
235 {
236  report += QLatin1String( "<tr>\n" );
237  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 );
238  report += QStringLiteral( "<td style='%1'>%2</td>\n" ).arg( mCellStyle, difference );
239  report += QStringLiteral( "<td style='%1'>%2</td>\n" ).arg( mCellStyle, tolerance );
240  report += QLatin1String( "</tr>" );
241 }
virtual int bandCount() const =0
Gets number of bands.
double maximumValue
The maximum cell value in the raster band.
virtual int ySize() const
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...
virtual double sourceNoDataValue(int bandNo) const
Value representing no data value.
QgsDataProvider * createProvider(const QString &providerKey, const QString &dataSource, const QgsDataProvider::ProviderOptions &options=QgsDataProvider::ProviderOptions())
Creates a new instance of a provider.
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.
QgsRectangle extent() const override=0
Returns the extent of the layer.
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
Returns 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)
Returns the band statistics.
QgsRasterBlock * block(int bandNo, const QgsRectangle &boundingBox, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
Setting options for creating vector data providers.
double minimumValue
The minimum cell value in the raster band.
virtual int xSize() const
Gets raster size.
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
Base class for raster data providers.