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