QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 = qobject_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  return false;
52  }
53 
54  //QgsRasterDataProvider* expectedProvider = QgsRasterLayer::loadProvider( expectedKey, expectedUri );
55  QgsRasterDataProvider *expectedProvider = qobject_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  return false;
60  }
61 
62  mReport += QStringLiteral( "Verified URI: %1<br>" ).arg( verifiedUri.replace( '&', QLatin1String( "&amp;" ) ) );
63  mReport += QStringLiteral( "Expected URI: %1<br>" ).arg( expectedUri.replace( '&', QLatin1String( "&amp;" ) ) );
64 
65  mReport += QLatin1String( "<br>" );
66  mReport += QStringLiteral( "<table style='%1'>\n" ).arg( mTabStyle );
67  mReport += compareHead();
68 
69  compare( QStringLiteral( "Band count" ), verifiedProvider->bandCount(), expectedProvider->bandCount(), mReport, ok );
70 
71  compare( QStringLiteral( "Width" ), verifiedProvider->xSize(), expectedProvider->xSize(), mReport, ok );
72  compare( QStringLiteral( "Height" ), verifiedProvider->ySize(), expectedProvider->ySize(), mReport, ok );
73 
74  compareRow( QStringLiteral( "Extent" ), verifiedProvider->extent().toString(), expectedProvider->extent().toString(), mReport, verifiedProvider->extent() == expectedProvider->extent() );
75 
76  if ( verifiedProvider->extent() != expectedProvider->extent() ) ok = false;
77 
78 
79  mReport += QLatin1String( "</table>\n" );
80 
81  if ( !ok ) return false;
82 
83  bool allOk = true;
84  for ( int band = 1; band <= expectedProvider->bandCount(); band++ )
85  {
86  mReport += QStringLiteral( "<h3>Band %1</h3>\n" ).arg( band );
87  mReport += QStringLiteral( "<table style='%1'>\n" ).arg( mTabStyle );
88  mReport += compareHead();
89 
90  // Data types may differ (?)
91  bool typesOk = true;
92  compare( QStringLiteral( "Source data type" ), verifiedProvider->sourceDataType( band ), expectedProvider->sourceDataType( band ), mReport, typesOk );
93  compare( QStringLiteral( "Data type" ), verifiedProvider->dataType( band ), expectedProvider->dataType( band ), mReport, typesOk );
94 
95  // Check nodata
96  bool noDataOk = true;
97  compare( QStringLiteral( "No data (NULL) value existence flag" ), verifiedProvider->sourceHasNoDataValue( band ), expectedProvider->sourceHasNoDataValue( band ), mReport, noDataOk );
98  if ( verifiedProvider->sourceHasNoDataValue( band ) && expectedProvider->sourceHasNoDataValue( band ) )
99  {
100  compare( QStringLiteral( "No data (NULL) value" ), verifiedProvider->sourceNoDataValue( band ), expectedProvider->sourceNoDataValue( band ), mReport, noDataOk );
101  }
102 
103  bool statsOk = true;
104  QgsRasterBandStats verifiedStats = verifiedProvider->bandStatistics( band );
105  QgsRasterBandStats expectedStats = expectedProvider->bandStatistics( band );
106 
107  // Min/max may 'slightly' differ, for big numbers however, the difference may
108  // be quite big, for example for Float32 with max -3.332e+38, the difference is 1.47338e+24
109  double tol = tolerance( expectedStats.minimumValue );
110  compare( QStringLiteral( "Minimum value" ), verifiedStats.minimumValue, expectedStats.minimumValue, mReport, statsOk, tol );
111  tol = tolerance( expectedStats.maximumValue );
112  compare( QStringLiteral( "Maximum value" ), verifiedStats.maximumValue, expectedStats.maximumValue, mReport, statsOk, tol );
113 
114  // TODO: enable once fixed (WCS excludes nulls but GDAL does not)
115  //compare( "Cells count", verifiedStats.elementCount, expectedStats.elementCount, mReport, statsOk );
116 
117  tol = tolerance( expectedStats.mean );
118  compare( QStringLiteral( "Mean" ), verifiedStats.mean, expectedStats.mean, mReport, statsOk, tol );
119 
120  // stdDev usually differ significantly
121  tol = tolerance( expectedStats.stdDev, 1 );
122  compare( QStringLiteral( "Standard deviation" ), verifiedStats.stdDev, expectedStats.stdDev, mReport, statsOk, tol );
123 
124  mReport += QLatin1String( "</table>" );
125  mReport += QLatin1String( "<br>" );
126 
127  if ( !statsOk || !typesOk || !noDataOk )
128  {
129  allOk = false;
130  // create values table anyway so that values are available
131  }
132 
133  mReport += QLatin1String( "<table><tr>" );
134  mReport += QLatin1String( "<td>Data comparison</td>" );
135  mReport += QStringLiteral( "<td style='%1 %2 border: 1px solid'>correct&nbsp;value</td>" ).arg( mCellStyle, mOkStyle );
136  mReport += QLatin1String( "<td></td>" );
137  mReport += QStringLiteral( "<td style='%1 %2 border: 1px solid'>wrong&nbsp;value<br>expected value</td></tr>" ).arg( mCellStyle, mErrStyle );
138  mReport += QLatin1String( "</tr></table>" );
139  mReport += QLatin1String( "<br>" );
140 
141  int width = expectedProvider->xSize();
142  int height = expectedProvider->ySize();
143  std::unique_ptr< QgsRasterBlock > expectedBlock( expectedProvider->block( band, expectedProvider->extent(), width, height ) );
144  std::unique_ptr< QgsRasterBlock > verifiedBlock( verifiedProvider->block( band, expectedProvider->extent(), width, height ) );
145 
146  if ( !expectedBlock || !expectedBlock->isValid() ||
147  !verifiedBlock || !verifiedBlock->isValid() )
148  {
149  allOk = false;
150  mReport += QLatin1String( "cannot read raster block" );
151  continue;
152  }
153 
154  // compare data values
155  QString htmlTable = QStringLiteral( "<table style='%1'>" ).arg( mTabStyle );
156  for ( int row = 0; row < height; row ++ )
157  {
158  htmlTable += QLatin1String( "<tr>" );
159  for ( int col = 0; col < width; col ++ )
160  {
161  bool cellOk = true;
162  double verifiedVal = verifiedBlock->value( row, col );
163  double expectedVal = expectedBlock->value( row, col );
164 
165  QString valStr;
166  if ( compare( verifiedVal, expectedVal, 0 ) )
167  {
168  valStr = QString::number( verifiedVal );
169  }
170  else
171  {
172  cellOk = false;
173  allOk = false;
174  valStr = QStringLiteral( "%1<br>%2" ).arg( verifiedVal ).arg( expectedVal );
175  }
176  htmlTable += QStringLiteral( "<td style='%1 %2'>%3</td>" ).arg( mCellStyle, cellOk ? mOkStyle : mErrStyle, valStr );
177  }
178  htmlTable += QLatin1String( "</tr>" );
179  }
180  htmlTable += QLatin1String( "</table>" );
181 
182  mReport += htmlTable;
183  }
184  delete verifiedProvider;
185  delete expectedProvider;
186  return allOk;
187 }
188 
189 void QgsRasterChecker::error( const QString &message, QString &report )
190 {
191  report += QStringLiteral( "<font style='%1'>Error: " ).arg( mErrMsgStyle );
192  report += message;
193  report += QLatin1String( "</font>" );
194 }
195 
196 double QgsRasterChecker::tolerance( double val, int places )
197 {
198  // float precision is about 7 decimal digits, double about 16
199  // default places = 6
200  return 1. * std::pow( 10, std::round( std::log10( std::fabs( val ) ) - places ) );
201 }
202 
203 QString QgsRasterChecker::compareHead()
204 {
205  QString html;
206  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 );
207  return html;
208 }
209 
210 void QgsRasterChecker::compare( const QString &paramName, int verifiedVal, int expectedVal, QString &report, bool &ok )
211 {
212  bool isEqual = verifiedVal == expectedVal;
213  compareRow( paramName, QString::number( verifiedVal ), QString::number( expectedVal ), report, isEqual, QString::number( verifiedVal - expectedVal ) );
214  if ( !isEqual )
215  ok = false;
216 }
217 
218 bool QgsRasterChecker::compare( double verifiedVal, double expectedVal, double tolerance )
219 {
220  // values may be nan
221  return ( std::isnan( verifiedVal ) && std::isnan( expectedVal ) ) || ( std::fabs( verifiedVal - expectedVal ) <= tolerance );
222 }
223 
224 void QgsRasterChecker::compare( const QString &paramName, double verifiedVal, double expectedVal, QString &report, bool &ok, double tolerance )
225 {
226  bool isNearEqual = compare( verifiedVal, expectedVal, tolerance );
227  compareRow( paramName, QString::number( verifiedVal ), QString::number( expectedVal ), report, isNearEqual, QString::number( verifiedVal - expectedVal ), QString::number( tolerance ) );
228  if ( !isNearEqual )
229  ok = false;
230 }
231 
232 void QgsRasterChecker::compareRow( const QString &paramName, const QString &verifiedVal, const QString &expectedVal, QString &report, bool ok, const QString &difference, const QString &tolerance )
233 {
234  report += QLatin1String( "<tr>\n" );
235  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 );
236  report += QStringLiteral( "<td style='%1'>%2</td>\n" ).arg( mCellStyle, difference );
237  report += QStringLiteral( "<td style='%1'>%2</td>\n" ).arg( mCellStyle, tolerance );
238  report += QLatin1String( "</tr>" );
239 }
virtual bool isValid() const =0
Returns true if this is a valid layer.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
The RasterBandStats struct is a container for statistics about a single raster band.
double mean
The mean cell value for the band. NO_DATA values are excluded.
double stdDev
The standard deviation of the cell values.
double minimumValue
The minimum cell value in the raster band.
double maximumValue
The maximum cell value in the raster band.
bool runTest(const QString &verifiedKey, QString verifiedUri, const QString &expectedKey, QString expectedUri)
Test using renderer to generate the image to be compared.
Base class for raster data providers.
virtual bool sourceHasNoDataValue(int bandNo) const
Returns true if source band has no data value.
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.
QgsRectangle extent() const override=0
Returns the extent of the layer.
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
QgsRasterBlock * block(int bandNo, const QgsRectangle &boundingBox, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
virtual int xSize() const
Gets raster size.
virtual int bandCount() const =0
Gets number of bands.
virtual QgsRasterBandStats bandStatistics(int bandNo, int stats=QgsRasterBandStats::All, const QgsRectangle &extent=QgsRectangle(), int sampleSize=0, QgsRasterBlockFeedback *feedback=nullptr)
Returns the band statistics.
virtual int ySize() const
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
Setting options for creating vector data providers.