Coverage Report

Created: 2026-07-14 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_raster_compare.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "raster compare" subcommand
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_raster_compare.h"
14
15
#include "cpl_conv.h"
16
#include "gdal_alg.h"
17
#include "gdal_priv.h"
18
19
#include <algorithm>
20
#include <array>
21
#include <cmath>
22
#include <limits>
23
#include <type_traits>
24
25
#if defined(__x86_64__) || defined(_M_X64)
26
#define USE_SSE2
27
#include <emmintrin.h>
28
#elif defined(USE_NEON_OPTIMIZATIONS)
29
#define USE_SSE2
30
#include "include_sse2neon.h"
31
#endif
32
33
//! @cond Doxygen_Suppress
34
35
#ifndef _
36
0
#define _(x) (x)
37
#endif
38
39
/************************************************************************/
40
/*       GDALRasterCompareAlgorithm::GDALRasterCompareAlgorithm()       */
41
/************************************************************************/
42
43
GDALRasterCompareAlgorithm::GDALRasterCompareAlgorithm(bool standaloneStep)
44
0
    : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
45
0
                                      ConstructorOptions()
46
0
                                          .SetStandaloneStep(standaloneStep)
47
0
                                          .SetInputDatasetMaxCount(1)
48
0
                                          .SetAddDefaultArguments(false))
49
0
{
50
0
    if (standaloneStep)
51
0
    {
52
0
        AddProgressArg();
53
0
    }
54
0
    else
55
0
    {
56
0
        AddRasterHiddenInputDatasetArg();
57
0
    }
58
59
0
    auto &referenceDatasetArg = AddArg("reference", 0, _("Reference dataset"),
60
0
                                       &m_referenceDataset, GDAL_OF_RASTER)
61
0
                                    .SetPositional()
62
0
                                    .SetRequired();
63
64
0
    SetAutoCompleteFunctionForFilename(referenceDatasetArg, GDAL_OF_RASTER);
65
66
0
    if (standaloneStep)
67
0
    {
68
0
        AddRasterInputArgs(/* openForMixedRasterVector = */ false,
69
0
                           /* hiddenForCLI = */ false);
70
0
    }
71
72
0
    AddArg("metric", 0, _("Comparison metric(s)"), &m_metrics)
73
0
        .SetChoices(METRIC_ALL, METRIC_NONE, METRIC_DIFF, METRIC_RMSD,
74
0
                    METRIC_PSNR)
75
0
        .SetDefault(METRIC_DEFAULT);
76
77
0
    AddArg("skip-all-optional", 0, _("Skip all optional comparisons"),
78
0
           &m_skipAllOptional);
79
0
    AddArg("skip-binary", 0, _("Skip binary file comparison"), &m_skipBinary);
80
0
    AddArg("skip-crs", 0, _("Skip CRS comparison"), &m_skipCRS);
81
0
    AddArg("skip-geotransform", 0, _("Skip geotransform comparison"),
82
0
           &m_skipGeotransform);
83
0
    AddArg("skip-overview", 0, _("Skip overview comparison"), &m_skipOverview);
84
0
    AddArg("skip-metadata", 0, _("Skip metadata comparison"), &m_skipMetadata);
85
0
    AddArg("skip-rpc", 0, _("Skip RPC metadata comparison"), &m_skipRPC);
86
0
    AddArg("skip-geolocation", 0, _("Skip Geolocation metadata comparison"),
87
0
           &m_skipGeolocation);
88
0
    AddArg("skip-subdataset", 0, _("Skip subdataset comparison"),
89
0
           &m_skipSubdataset);
90
91
0
    AddOutputStringArg(&m_output);
92
93
0
    AddArg("return-code", 0, _("Return code"), &m_retCode)
94
0
        .SetHiddenForCLI()
95
0
        .SetIsInput(false)
96
0
        .SetIsOutput(true);
97
0
}
98
99
/************************************************************************/
100
/*             GDALRasterCompareAlgorithm::CRSComparison()              */
101
/************************************************************************/
102
103
void GDALRasterCompareAlgorithm::CRSComparison(
104
    std::vector<std::string> &aosReport, GDALDataset *poRefDS,
105
    GDALDataset *poInputDS)
106
0
{
107
0
    const auto poRefCRS = poRefDS->GetSpatialRef();
108
0
    const auto poInputCRS = poInputDS->GetSpatialRef();
109
110
0
    if (poRefCRS == nullptr)
111
0
    {
112
0
        if (poInputCRS)
113
0
        {
114
0
            aosReport.push_back(
115
0
                "Reference dataset has no CRS, but input dataset has one.");
116
0
        }
117
0
        return;
118
0
    }
119
120
0
    if (poInputCRS == nullptr)
121
0
    {
122
0
        aosReport.push_back(
123
0
            "Reference dataset has a CRS, but input dataset has none.");
124
0
        return;
125
0
    }
126
127
0
    if (poRefCRS->IsSame(poInputCRS))
128
0
        return;
129
130
0
    const char *apszOptions[] = {"FORMAT=WKT2_2019", nullptr};
131
0
    const auto poRefWKT = poRefCRS->exportToWkt(apszOptions);
132
0
    const auto poInputWKT = poInputCRS->exportToWkt(apszOptions);
133
0
    aosReport.push_back(
134
0
        "Reference and input CRS are not equivalent. Reference one is '" +
135
0
        poRefWKT + "'. Input one is '" + poInputWKT + "'");
136
0
}
137
138
/************************************************************************/
139
/*         GDALRasterCompareAlgorithm::GeotransformComparison()         */
140
/************************************************************************/
141
142
void GDALRasterCompareAlgorithm::GeoTransformComparison(
143
    std::vector<std::string> &aosReport, GDALDataset *poRefDS,
144
    GDALDataset *poInputDS)
145
0
{
146
0
    GDALGeoTransform refGT;
147
0
    CPLErr eErr1 = poRefDS->GetGeoTransform(refGT);
148
0
    GDALGeoTransform inputGT;
149
0
    CPLErr eErr2 = poInputDS->GetGeoTransform(inputGT);
150
0
    if (eErr1 == CE_Failure && eErr2 == CE_Failure)
151
0
        return;
152
153
0
    if (eErr1 == CE_Failure && eErr2 == CE_None)
154
0
    {
155
0
        aosReport.push_back(
156
0
            "Reference dataset has no geotransform, but input one has one.");
157
0
        return;
158
0
    }
159
160
0
    if (eErr1 == CE_None && eErr2 == CE_Failure)
161
0
    {
162
0
        aosReport.push_back(
163
0
            "Reference dataset has a geotransform, but input one has none.");
164
0
        return;
165
0
    }
166
167
0
    for (int i = 0; i < 6; ++i)
168
0
    {
169
0
        if ((refGT[i] != 0 &&
170
0
             std::fabs(refGT[i] - inputGT[i]) > 1e-10 * std::fabs(refGT[i])) ||
171
0
            (refGT[i] == 0 && std::fabs(refGT[i] - inputGT[i]) > 1e-10))
172
0
        {
173
0
            std::string s = "Geotransform of reference and input dataset are "
174
0
                            "not equivalent. Reference geotransform is (";
175
0
            for (int j = 0; j < 6; ++j)
176
0
            {
177
0
                if (j > 0)
178
0
                    s += ',';
179
0
                s += std::to_string(refGT[j]);
180
0
            }
181
0
            s += "). Input geotransform is (";
182
0
            for (int j = 0; j < 6; ++j)
183
0
            {
184
0
                if (j > 0)
185
0
                    s += ',';
186
0
                s += std::to_string(inputGT[j]);
187
0
            }
188
0
            s += ')';
189
0
            aosReport.push_back(std::move(s));
190
0
            return;
191
0
        }
192
0
    }
193
0
}
194
195
#if defined(__GNUC__) && !defined(__clang__)
196
#pragma GCC push_options
197
#pragma GCC optimize("O3")
198
#endif
199
200
/************************************************************************/
201
/*                                Diff()                                */
202
/************************************************************************/
203
204
template <class T> CPL_NOSANITIZE_UNSIGNED_INT_OVERFLOW static T Diff(T a, T b)
205
0
{
206
0
    return a - b;
207
0
}
Unexecuted instantiation: gdalalg_raster_compare.cpp:unsigned char Diff<unsigned char>(unsigned char, unsigned char)
Unexecuted instantiation: gdalalg_raster_compare.cpp:unsigned short Diff<unsigned short>(unsigned short, unsigned short)
Unexecuted instantiation: gdalalg_raster_compare.cpp:unsigned int Diff<unsigned int>(unsigned int, unsigned int)
Unexecuted instantiation: gdalalg_raster_compare.cpp:unsigned long Diff<unsigned long>(unsigned long, unsigned long)
Unexecuted instantiation: gdalalg_raster_compare.cpp:float Diff<float>(float, float)
Unexecuted instantiation: gdalalg_raster_compare.cpp:double Diff<double>(double, double)
208
209
/************************************************************************/
210
/*                           CompareVectors()                           */
211
/************************************************************************/
212
213
template <class T, class Tdiff, bool bIsComplex>
214
static void CompareVectors(size_t nValCount, const T *refValues,
215
                           const T *inputValues, uint64_t &countDiffPixels,
216
                           Tdiff &maxDiffValue)
217
0
{
218
0
    constexpr bool bIsFloatingPoint = std::is_floating_point_v<T>;
219
    if constexpr (bIsComplex)
220
0
    {
221
0
        for (size_t i = 0; i < nValCount; ++i)
222
0
        {
223
            if constexpr (bIsFloatingPoint)
224
0
            {
225
0
                static_assert(std::is_same_v<T, Tdiff>);
226
0
                if (std::isnan(refValues[2 * i]) &&
227
0
                    std::isnan(inputValues[2 * i]) &&
228
0
                    std::isnan(refValues[2 * i + 1]) &&
229
0
                    std::isnan(inputValues[2 * i + 1]))
230
0
                {
231
0
                    continue;
232
0
                }
233
0
            }
234
235
0
            if (refValues[2 * i] != inputValues[2 * i] ||
236
0
                refValues[2 * i + 1] != inputValues[2 * i + 1])
237
0
            {
238
0
                const Tdiff diff =
239
0
                    std::hypot(static_cast<Tdiff>(refValues[2 * i]) -
240
0
                                   static_cast<Tdiff>(inputValues[2 * i]),
241
0
                               static_cast<Tdiff>(refValues[2 * i + 1]) -
242
0
                                   static_cast<Tdiff>(inputValues[2 * i + 1]));
243
0
                ++countDiffPixels;
244
0
                if (diff > maxDiffValue)
245
0
                    maxDiffValue = diff;
246
0
            }
247
0
        }
248
    }
249
    else
250
0
    {
251
0
        static_assert(sizeof(Tdiff) == sizeof(T));
252
0
        size_t i = 0;
253
0
#ifdef USE_SSE2
254
        if constexpr (std::is_same_v<T, float>)
255
0
        {
256
0
            static_assert(std::is_same_v<T, Tdiff>);
257
258
0
            auto vMaxDiff = _mm_setzero_ps();
259
260
            // Mask for absolute value (clears the sign bit)
261
0
            const auto absMask = _mm_castsi128_ps(
262
0
                _mm_set1_epi32(std::numeric_limits<int32_t>::max()));
263
264
0
            constexpr size_t VALS_PER_REG = sizeof(vMaxDiff) / sizeof(T);
265
0
            while (i + VALS_PER_REG <= nValCount)
266
0
            {
267
0
                auto vCountDiff = _mm_setzero_si128();
268
269
                // We can do a maximum of std::numeric_limits<uint32_t>::max()
270
                // accumulations into vCountDiff
271
0
                const size_t nInnerLimit = [i, nValCount](size_t valsPerReg)
272
0
                {
273
                    if constexpr (sizeof(size_t) > sizeof(uint32_t))
274
0
                    {
275
0
                        return std::min(
276
0
                            nValCount - valsPerReg,
277
0
                            i + std::numeric_limits<uint32_t>::max() *
278
0
                                    valsPerReg);
279
                    }
280
                    else
281
                    {
282
                        return nValCount - valsPerReg;
283
                    }
284
0
                }(VALS_PER_REG);
285
286
0
                for (; i <= nInnerLimit; i += VALS_PER_REG)
287
0
                {
288
0
                    const auto a = _mm_loadu_ps(refValues + i);
289
0
                    const auto b = _mm_loadu_ps(inputValues + i);
290
291
                    // Compute absolute value of difference
292
0
                    const auto absDiff = _mm_and_ps(_mm_sub_ps(a, b), absMask);
293
294
                    // Update vMaxDiff
295
0
                    const auto aIsNan = _mm_cmpunord_ps(a, a);
296
0
                    const auto bIsNan = _mm_cmpunord_ps(b, b);
297
0
                    const auto valNotEqual = _mm_andnot_ps(
298
0
                        _mm_or_ps(aIsNan, bIsNan), _mm_cmpneq_ps(a, b));
299
0
                    vMaxDiff =
300
0
                        _mm_max_ps(vMaxDiff, _mm_and_ps(absDiff, valNotEqual));
301
302
                    // Update vCountDiff
303
0
                    const auto nanMisMatch = _mm_xor_ps(aIsNan, bIsNan);
304
                    // if nanMisMatch OR (both values not NaN and a != b)
305
0
                    const auto maskIsDiff = _mm_or_ps(nanMisMatch, valNotEqual);
306
0
                    const auto shiftedMaskDiff =
307
0
                        _mm_srli_epi32(_mm_castps_si128(maskIsDiff), 31);
308
0
                    vCountDiff = _mm_add_epi32(vCountDiff, shiftedMaskDiff);
309
0
                }
310
311
                // Horizontal add into countDiffPixels
312
0
                uint32_t anCountDiff[VALS_PER_REG];
313
0
                _mm_storeu_si128(reinterpret_cast<__m128i *>(anCountDiff),
314
0
                                 vCountDiff);
315
0
                for (size_t j = 0; j < VALS_PER_REG; ++j)
316
0
                {
317
0
                    countDiffPixels += anCountDiff[j];
318
0
                }
319
0
            }
320
321
            // Horizontal max into maxDiffValue
322
0
            float afMaxDiffValue[VALS_PER_REG];
323
0
            _mm_storeu_ps(afMaxDiffValue, vMaxDiff);
324
0
            for (size_t j = 0; j < VALS_PER_REG; ++j)
325
0
            {
326
0
                CPLAssert(!std::isnan(afMaxDiffValue[j]));
327
0
                maxDiffValue = std::max(maxDiffValue, afMaxDiffValue[j]);
328
0
            }
329
0
        }
330
0
#endif
331
        if constexpr (bIsFloatingPoint)
332
0
        {
333
0
            static_assert(std::is_same_v<T, Tdiff>);
334
0
            for (; i < nValCount; ++i)
335
0
            {
336
0
                if (std::isnan(refValues[i]))
337
0
                {
338
0
                    if (!std::isnan(inputValues[i]))
339
0
                    {
340
0
                        ++countDiffPixels;
341
0
                    }
342
0
                    continue;
343
0
                }
344
0
                else if (std::isnan(inputValues[i]))
345
0
                {
346
0
                    ++countDiffPixels;
347
0
                    continue;
348
0
                }
349
0
                else if (refValues[i] == inputValues[i])
350
0
                {
351
0
                    continue;
352
0
                }
353
354
0
                const Tdiff diff =
355
0
                    refValues[i] >= inputValues[i]
356
0
                        ? Diff(static_cast<Tdiff>(refValues[i]),
357
0
                               static_cast<Tdiff>(inputValues[i]))
358
0
                        : Diff(static_cast<Tdiff>(inputValues[i]),
359
0
                               static_cast<Tdiff>(refValues[i]));
360
0
                if (diff > 0)
361
0
                {
362
0
                    ++countDiffPixels;
363
0
                    if (diff > maxDiffValue)
364
0
                        maxDiffValue = diff;
365
0
                }
366
0
            }
367
        }
368
        else
369
0
        {
370
0
            static_assert(std::is_unsigned_v<Tdiff>);
371
0
            while (i < nValCount)
372
0
            {
373
                // Autovectorizer friendly inner loop (GCC, clang, ICX),
374
                // by making sure it increases countDiffLocal on the same size
375
                // as Tdiff.
376
377
0
                Tdiff countDiffLocal = 0;
378
0
                const size_t innerLimit = [i, nValCount]()
379
0
                {
380
                    if constexpr (sizeof(Tdiff) < sizeof(size_t))
381
0
                    {
382
0
                        return std::min(nValCount,
383
0
                                        i + std::numeric_limits<Tdiff>::max());
384
                    }
385
                    else
386
0
                    {
387
0
                        (void)i;
388
0
                        return nValCount;
389
0
                    }
390
0
                }();
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<unsigned char, unsigned char, false>(unsigned long, unsigned char const*, unsigned char const*, unsigned long&, unsigned char&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<signed char, unsigned char, false>(unsigned long, signed char const*, signed char const*, unsigned long&, unsigned char&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<unsigned short, unsigned short, false>(unsigned long, unsigned short const*, unsigned short const*, unsigned long&, unsigned short&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<short, unsigned short, false>(unsigned long, short const*, short const*, unsigned long&, unsigned short&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<unsigned int, unsigned int, false>(unsigned long, unsigned int const*, unsigned int const*, unsigned long&, unsigned int&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<int, unsigned int, false>(unsigned long, int const*, int const*, unsigned long&, unsigned int&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<unsigned long, unsigned long, false>(unsigned long, unsigned long const*, unsigned long const*, unsigned long&, unsigned long&)::{lambda()#1}::operator()() const
Unexecuted instantiation: gdalalg_raster_compare.cpp:CompareVectors<long, unsigned long, false>(unsigned long, long const*, long const*, unsigned long&, unsigned long&)::{lambda()#1}::operator()() const
391
0
                for (; i < innerLimit; ++i)
392
0
                {
393
0
                    const Tdiff diff =
394
0
                        refValues[i] >= inputValues[i]
395
0
                            ? Diff(static_cast<Tdiff>(refValues[i]),
396
0
                                   static_cast<Tdiff>(inputValues[i]))
397
0
                            : Diff(static_cast<Tdiff>(inputValues[i]),
398
0
                                   static_cast<Tdiff>(refValues[i]));
399
0
                    countDiffLocal += (diff > 0);
400
0
                    maxDiffValue = std::max(maxDiffValue, diff);
401
0
                }
402
0
                countDiffPixels += countDiffLocal;
403
0
            }
404
0
        }
405
0
    }
406
0
}
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<unsigned char, unsigned char, false>(unsigned long, unsigned char const*, unsigned char const*, unsigned long&, unsigned char&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<signed char, unsigned char, false>(unsigned long, signed char const*, signed char const*, unsigned long&, unsigned char&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<unsigned short, unsigned short, false>(unsigned long, unsigned short const*, unsigned short const*, unsigned long&, unsigned short&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<short, unsigned short, false>(unsigned long, short const*, short const*, unsigned long&, unsigned short&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<unsigned int, unsigned int, false>(unsigned long, unsigned int const*, unsigned int const*, unsigned long&, unsigned int&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<int, unsigned int, false>(unsigned long, int const*, int const*, unsigned long&, unsigned int&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<unsigned long, unsigned long, false>(unsigned long, unsigned long const*, unsigned long const*, unsigned long&, unsigned long&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<long, unsigned long, false>(unsigned long, long const*, long const*, unsigned long&, unsigned long&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<float, float, false>(unsigned long, float const*, float const*, unsigned long&, float&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<double, double, false>(unsigned long, double const*, double const*, unsigned long&, double&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<short, float, true>(unsigned long, short const*, short const*, unsigned long&, float&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<int, double, true>(unsigned long, int const*, int const*, unsigned long&, double&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<float, float, true>(unsigned long, float const*, float const*, unsigned long&, float&)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void CompareVectors<double, double, true>(unsigned long, double const*, double const*, unsigned long&, double&)
407
408
/************************************************************************/
409
/*                       DatasetPixelComparison()                       */
410
/************************************************************************/
411
412
template <class T, class Tdiff, bool bIsComplex>
413
static void DatasetPixelComparison(std::vector<std::string> &aosReport,
414
                                   GDALDataset *poRefDS, GDALDataset *poInputDS,
415
                                   GDALDataType eReqDT,
416
                                   GDALProgressFunc pfnProgress,
417
                                   void *pProgressData)
418
0
{
419
0
    std::vector<T> refValues;
420
0
    std::vector<T> inputValues;
421
422
0
    CPLAssert(GDALDataTypeIsComplex(eReqDT) == bIsComplex);
423
424
0
    const uint64_t nTotalPixels =
425
0
        static_cast<uint64_t>(poRefDS->GetRasterXSize()) *
426
0
        poRefDS->GetRasterYSize();
427
0
    uint64_t nIterPixels = 0;
428
429
0
    constexpr int nValPerPixel = bIsComplex ? 2 : 1;
430
0
    const int nBands = poRefDS->GetRasterCount();
431
432
0
    std::vector<Tdiff> maxDiffValue(nBands, 0);
433
0
    std::vector<uint64_t> countDiffPixels(nBands, 0);
434
435
0
    size_t nMaxSize = 0;
436
0
    const GIntBig nUsableRAM = CPLGetUsablePhysicalRAM() / 10;
437
0
    if (nUsableRAM > 0)
438
0
        nMaxSize = static_cast<size_t>(nUsableRAM);
439
440
0
    for (const auto &window : GDALRasterBand::WindowIteratorWrapper(
441
0
             *(poRefDS->GetRasterBand(1)), *(poInputDS->GetRasterBand(1)),
442
0
             nMaxSize))
443
0
    {
444
0
        const size_t nValCount =
445
0
            static_cast<size_t>(window.nXSize) * window.nYSize;
446
0
        const size_t nArraySize = nValCount * nValPerPixel * nBands;
447
0
        try
448
0
        {
449
0
            if (refValues.size() < nArraySize)
450
0
            {
451
0
                refValues.resize(nArraySize);
452
0
                inputValues.resize(nArraySize);
453
0
            }
454
0
        }
455
0
        catch (const std::exception &)
456
0
        {
457
0
            CPLError(CE_Failure, CPLE_OutOfMemory,
458
0
                     "Out of memory allocating temporary arrays");
459
0
            aosReport.push_back("Out of memory allocating temporary arrays");
460
0
            return;
461
0
        }
462
463
0
        if (poRefDS->RasterIO(GF_Read, window.nXOff, window.nYOff,
464
0
                              window.nXSize, window.nYSize, refValues.data(),
465
0
                              window.nXSize, window.nYSize, eReqDT, nBands,
466
0
                              nullptr, 0, 0, 0, nullptr) == CE_None &&
467
0
            poInputDS->RasterIO(
468
0
                GF_Read, window.nXOff, window.nYOff, window.nXSize,
469
0
                window.nYSize, inputValues.data(), window.nXSize, window.nYSize,
470
0
                eReqDT, nBands, nullptr, 0, 0, 0, nullptr) == CE_None)
471
0
        {
472
0
            for (int i = 0; i < nBands; ++i)
473
0
            {
474
0
                CompareVectors<T, Tdiff, bIsComplex>(
475
0
                    nValCount, refValues.data() + i * nValCount * nValPerPixel,
476
0
                    inputValues.data() + i * nValCount * nValPerPixel,
477
0
                    countDiffPixels[i], maxDiffValue[i]);
478
0
            }
479
0
        }
480
0
        else
481
0
        {
482
0
            aosReport.push_back("I/O error when comparing pixel values");
483
0
        }
484
485
0
        if (pfnProgress)
486
0
        {
487
0
            nIterPixels += nValCount;
488
0
            if (!pfnProgress(static_cast<double>(nIterPixels) /
489
0
                                 static_cast<double>(nTotalPixels),
490
0
                             "", pProgressData))
491
0
            {
492
0
                CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
493
0
                break;
494
0
            }
495
0
        }
496
0
    }
497
0
    for (int i = 0; i < nBands; ++i)
498
0
    {
499
0
        if (countDiffPixels[i])
500
0
        {
501
0
            aosReport.push_back(
502
0
                "Band " + std::to_string(i + 1) +
503
0
                ": pixels differing: " + std::to_string(countDiffPixels[i]));
504
0
            aosReport.push_back("Band " + std::to_string(i + 1) +
505
0
                                ": maximum pixel value difference: " +
506
0
                                std::to_string(maxDiffValue[i]));
507
0
        }
508
0
    }
509
0
}
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<unsigned char, unsigned char, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<signed char, unsigned char, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<unsigned short, unsigned short, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<short, unsigned short, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<unsigned int, unsigned int, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<int, unsigned int, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<unsigned long, unsigned long, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<long, unsigned long, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<float, float, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<double, double, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<short, float, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<int, double, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<float, float, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void DatasetPixelComparison<double, double, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, GDALDataset*, GDALDataset*, GDALDataType, int (*)(double, char const*, void*), void*)
510
511
/************************************************************************/
512
/*           GDALRasterCompareAlgorithm::DatasetComparison()            */
513
/************************************************************************/
514
515
void GDALRasterCompareAlgorithm::DatasetComparison(
516
    std::vector<std::string> &aosReport, GDALDataset *poRefDS,
517
    GDALDataset *poInputDS, GDALProgressFunc pfnProgress, void *pProgressData)
518
0
{
519
0
    if (!m_skipCRS)
520
0
    {
521
0
        CRSComparison(aosReport, poRefDS, poInputDS);
522
0
    }
523
524
0
    if (!m_skipGeotransform)
525
0
    {
526
0
        GeoTransformComparison(aosReport, poRefDS, poInputDS);
527
0
    }
528
529
0
    bool ret = true;
530
0
    if (poRefDS->GetRasterCount() != poInputDS->GetRasterCount())
531
0
    {
532
0
        aosReport.push_back("Reference dataset has " +
533
0
                            std::to_string(poRefDS->GetRasterCount()) +
534
0
                            " band(s), but input dataset has " +
535
0
                            std::to_string(poInputDS->GetRasterCount()));
536
0
        ret = false;
537
0
    }
538
539
0
    if (poRefDS->GetRasterXSize() != poInputDS->GetRasterXSize())
540
0
    {
541
0
        aosReport.push_back("Reference dataset width is " +
542
0
                            std::to_string(poRefDS->GetRasterXSize()) +
543
0
                            ", but input dataset width is " +
544
0
                            std::to_string(poInputDS->GetRasterXSize()));
545
0
        ret = false;
546
0
    }
547
548
0
    if (poRefDS->GetRasterYSize() != poInputDS->GetRasterYSize())
549
0
    {
550
0
        aosReport.push_back("Reference dataset height is " +
551
0
                            std::to_string(poRefDS->GetRasterYSize()) +
552
0
                            ", but input dataset height is " +
553
0
                            std::to_string(poInputDS->GetRasterYSize()));
554
0
        ret = false;
555
0
    }
556
557
0
    if (!m_skipMetadata)
558
0
    {
559
0
        MetadataComparison(aosReport, "(dataset default metadata domain)",
560
0
                           poRefDS->GetMetadata(), poInputDS->GetMetadata());
561
0
    }
562
563
0
    if (!m_skipRPC)
564
0
    {
565
0
        MetadataComparison(aosReport, GDAL_MDD_RPC,
566
0
                           poRefDS->GetMetadata(GDAL_MDD_RPC),
567
0
                           poInputDS->GetMetadata(GDAL_MDD_RPC));
568
0
    }
569
570
0
    if (!m_skipGeolocation)
571
0
    {
572
0
        MetadataComparison(aosReport, GDAL_MDD_GEOLOCATION,
573
0
                           poRefDS->GetMetadata(GDAL_MDD_GEOLOCATION),
574
0
                           poInputDS->GetMetadata(GDAL_MDD_GEOLOCATION));
575
0
    }
576
577
0
    if (!ret)
578
0
        return;
579
580
0
    const int nBands = poRefDS->GetRasterCount();
581
582
0
    bool doBandBasedPixelComparison = true;
583
    // Do not do band-by-band pixel difference if there are too many interleaved
584
    // bands as this could be extremely slow
585
0
    if (nBands > 10 && !HasMetric(METRIC_NONE))
586
0
    {
587
0
        const char *pszRefInterleave = poRefDS->GetMetadataItem(
588
0
            GDALMD_INTERLEAVE, GDAL_MDD_IMAGE_STRUCTURE);
589
0
        const char *pszInputInterleave = poInputDS->GetMetadataItem(
590
0
            GDALMD_INTERLEAVE, GDAL_MDD_IMAGE_STRUCTURE);
591
0
        if ((pszRefInterleave && EQUAL(pszRefInterleave, "PIXEL")) ||
592
0
            (pszInputInterleave && EQUAL(pszInputInterleave, "PIXEL")))
593
0
        {
594
0
            if (m_metrics != std::vector<std::string>{METRIC_DIFF})
595
0
            {
596
0
                CPLError(CE_Failure, CPLE_AppDefined,
597
0
                         "Given the pixel-interleaved nature of the dataset, "
598
0
                         "only --metrics=diff would be supported");
599
0
            }
600
0
            doBandBasedPixelComparison = false;
601
0
        }
602
0
    }
603
604
0
    for (int i = 0; i < nBands; ++i)
605
0
    {
606
0
        void *pScaledProgress = GDALCreateScaledProgress(
607
0
            static_cast<double>(i) / nBands,
608
0
            static_cast<double>(i + 1) / nBands, pfnProgress, pProgressData);
609
0
        BandComparison(
610
0
            aosReport, std::to_string(i + 1), doBandBasedPixelComparison,
611
0
            poRefDS->GetRasterBand(i + 1), poInputDS->GetRasterBand(i + 1),
612
0
            pScaledProgress ? GDALScaledProgress : nullptr, pScaledProgress);
613
0
        GDALDestroyScaledProgress(pScaledProgress);
614
0
    }
615
616
0
    if (!doBandBasedPixelComparison && HasMetric(METRIC_DIFF))
617
0
    {
618
0
        const auto eReqDT =
619
0
            GDALDataTypeUnion(poRefDS->GetRasterBand(1)->GetRasterDataType(),
620
0
                              poInputDS->GetRasterBand(1)->GetRasterDataType());
621
0
        switch (eReqDT)
622
0
        {
623
0
            case GDT_UInt8:
624
0
                DatasetPixelComparison<uint8_t, uint8_t, false>(
625
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
626
0
                    pProgressData);
627
0
                break;
628
0
            case GDT_Int8:
629
0
                DatasetPixelComparison<int8_t, uint8_t, false>(
630
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
631
0
                    pProgressData);
632
0
                break;
633
0
            case GDT_UInt16:
634
0
                DatasetPixelComparison<uint16_t, uint16_t, false>(
635
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
636
0
                    pProgressData);
637
0
                break;
638
0
            case GDT_Int16:
639
0
                DatasetPixelComparison<int16_t, uint16_t, false>(
640
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
641
0
                    pProgressData);
642
0
                break;
643
0
            case GDT_UInt32:
644
0
                DatasetPixelComparison<uint32_t, uint32_t, false>(
645
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
646
0
                    pProgressData);
647
0
                break;
648
0
            case GDT_Int32:
649
0
                DatasetPixelComparison<int32_t, uint32_t, false>(
650
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
651
0
                    pProgressData);
652
0
                break;
653
0
            case GDT_UInt64:
654
0
                DatasetPixelComparison<uint64_t, uint64_t, false>(
655
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
656
0
                    pProgressData);
657
0
                break;
658
0
            case GDT_Int64:
659
0
                DatasetPixelComparison<int64_t, uint64_t, false>(
660
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
661
0
                    pProgressData);
662
0
                break;
663
0
            case GDT_Float16:
664
0
            case GDT_Float32:
665
0
                DatasetPixelComparison<float, float, false>(
666
0
                    aosReport, poRefDS, poInputDS, GDT_Float32, pfnProgress,
667
0
                    pProgressData);
668
0
                break;
669
0
            case GDT_Float64:
670
0
                DatasetPixelComparison<double, double, false>(
671
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
672
0
                    pProgressData);
673
0
                break;
674
0
            case GDT_CInt16:
675
0
                DatasetPixelComparison<int16_t, float, true>(
676
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
677
0
                    pProgressData);
678
0
                break;
679
0
            case GDT_CInt32:
680
0
                DatasetPixelComparison<int32_t, double, true>(
681
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
682
0
                    pProgressData);
683
0
                break;
684
0
            case GDT_CFloat16:
685
0
            case GDT_CFloat32:
686
0
                DatasetPixelComparison<float, float, true>(
687
0
                    aosReport, poRefDS, poInputDS, GDT_CFloat32, pfnProgress,
688
0
                    pProgressData);
689
0
                break;
690
0
            case GDT_CFloat64:
691
0
                DatasetPixelComparison<double, double, true>(
692
0
                    aosReport, poRefDS, poInputDS, eReqDT, pfnProgress,
693
0
                    pProgressData);
694
0
                break;
695
0
            case GDT_Unknown:
696
0
            case GDT_TypeCount:
697
0
                break;
698
0
        }
699
0
    }
700
0
}
701
702
/************************************************************************/
703
/*                           ComparePixels()                            */
704
/************************************************************************/
705
706
template <class T, class Tdiff, bool bIsComplex>
707
static void ComparePixels(std::vector<std::string> &aosReport,
708
                          const std::string &bandId, GDALRasterBand *poRefBand,
709
                          GDALRasterBand *poInputBand, GDALDataType eReqDT,
710
                          GDALProgressFunc pfnProgress, void *pProgressData)
711
0
{
712
0
    std::vector<T> refValues;
713
0
    std::vector<T> inputValues;
714
0
    Tdiff maxDiffValue = 0;
715
0
    uint64_t countDiffPixels = 0;
716
717
0
    CPLAssert(GDALDataTypeIsComplex(eReqDT) == bIsComplex);
718
0
    const uint64_t nTotalPixels =
719
0
        static_cast<uint64_t>(poRefBand->GetXSize()) * poRefBand->GetYSize();
720
0
    uint64_t nIterPixels = 0;
721
722
0
    constexpr int nValPerPixel = bIsComplex ? 2 : 1;
723
724
0
    size_t nMaxSize = 0;
725
0
    const GIntBig nUsableRAM = CPLGetUsablePhysicalRAM() / 10;
726
0
    if (nUsableRAM > 0)
727
0
        nMaxSize = static_cast<size_t>(nUsableRAM);
728
729
0
    for (const auto &window : GDALRasterBand::WindowIteratorWrapper(
730
0
             *poRefBand, *poInputBand, nMaxSize))
731
0
    {
732
0
        const size_t nValCount =
733
0
            static_cast<size_t>(window.nXSize) * window.nYSize;
734
0
        const size_t nArraySize = nValCount * nValPerPixel;
735
0
        try
736
0
        {
737
0
            if (refValues.size() < nArraySize)
738
0
            {
739
0
                refValues.resize(nArraySize);
740
0
                inputValues.resize(nArraySize);
741
0
            }
742
0
        }
743
0
        catch (const std::exception &)
744
0
        {
745
0
            CPLError(CE_Failure, CPLE_OutOfMemory,
746
0
                     "Out of memory allocating temporary arrays");
747
0
            aosReport.push_back("Out of memory allocating temporary arrays");
748
0
            return;
749
0
        }
750
751
0
        if (poRefBand->RasterIO(GF_Read, window.nXOff, window.nYOff,
752
0
                                window.nXSize, window.nYSize, refValues.data(),
753
0
                                window.nXSize, window.nYSize, eReqDT, 0, 0,
754
0
                                nullptr) == CE_None &&
755
0
            poInputBand->RasterIO(
756
0
                GF_Read, window.nXOff, window.nYOff, window.nXSize,
757
0
                window.nYSize, inputValues.data(), window.nXSize, window.nYSize,
758
0
                eReqDT, 0, 0, nullptr) == CE_None)
759
0
        {
760
0
            CompareVectors<T, Tdiff, bIsComplex>(nValCount, refValues.data(),
761
0
                                                 inputValues.data(),
762
0
                                                 countDiffPixels, maxDiffValue);
763
0
        }
764
0
        else
765
0
        {
766
0
            aosReport.push_back("I/O error when comparing pixel values");
767
0
        }
768
769
0
        if (pfnProgress)
770
0
        {
771
0
            nIterPixels += nValCount;
772
0
            if (!pfnProgress(static_cast<double>(nIterPixels) /
773
0
                                 static_cast<double>(nTotalPixels),
774
0
                             "", pProgressData))
775
0
            {
776
0
                CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
777
0
                break;
778
0
            }
779
0
        }
780
0
    }
781
0
    if (countDiffPixels)
782
0
    {
783
0
        aosReport.push_back("Band " + bandId + ": pixels differing: " +
784
0
                            std::to_string(countDiffPixels));
785
786
0
        std::string reportMessage("Band ");
787
0
        reportMessage += bandId;
788
0
        reportMessage += ": maximum pixel value difference: ";
789
        if constexpr (std::is_floating_point_v<T>)
790
0
        {
791
0
            if (std::isinf(maxDiffValue))
792
0
                reportMessage += "inf";
793
0
            else if (std::isnan(maxDiffValue))
794
0
                reportMessage += "nan";
795
0
            else
796
0
                reportMessage += std::to_string(maxDiffValue);
797
        }
798
        else
799
0
        {
800
0
            reportMessage += std::to_string(maxDiffValue);
801
0
        }
802
0
        aosReport.push_back(std::move(reportMessage));
803
0
    }
804
0
}
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<unsigned char, unsigned char, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<signed char, unsigned char, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<unsigned short, unsigned short, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<short, unsigned short, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<unsigned int, unsigned int, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<int, unsigned int, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<unsigned long, unsigned long, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<long, unsigned long, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<float, float, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<double, double, false>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<short, float, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<int, double, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<float, float, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
Unexecuted instantiation: gdalalg_raster_compare.cpp:void ComparePixels<double, double, true>(std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, GDALRasterBand*, GDALRasterBand*, GDALDataType, int (*)(double, char const*, void*), void*)
805
806
/************************************************************************/
807
/*                           ComparePixels()                            */
808
/************************************************************************/
809
810
static void ComparePixels(std::vector<std::string> &aosReport,
811
                          const std::string &bandId, GDALRasterBand *poRefBand,
812
                          GDALRasterBand *poInputBand,
813
                          GDALProgressFunc pfnProgress, void *pProgressData)
814
0
{
815
0
    const auto eReqDT = GDALDataTypeUnion(poRefBand->GetRasterDataType(),
816
0
                                          poInputBand->GetRasterDataType());
817
0
    switch (eReqDT)
818
0
    {
819
0
        case GDT_UInt8:
820
0
            ComparePixels<uint8_t, uint8_t, false>(aosReport, bandId, poRefBand,
821
0
                                                   poInputBand, eReqDT,
822
0
                                                   pfnProgress, pProgressData);
823
0
            break;
824
0
        case GDT_Int8:
825
0
            ComparePixels<int8_t, uint8_t, false>(aosReport, bandId, poRefBand,
826
0
                                                  poInputBand, eReqDT,
827
0
                                                  pfnProgress, pProgressData);
828
0
            break;
829
0
        case GDT_UInt16:
830
0
            ComparePixels<uint16_t, uint16_t, false>(
831
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
832
0
                pProgressData);
833
0
            break;
834
0
        case GDT_Int16:
835
0
            ComparePixels<int16_t, uint16_t, false>(
836
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
837
0
                pProgressData);
838
0
            break;
839
0
        case GDT_UInt32:
840
0
            ComparePixels<uint32_t, uint32_t, false>(
841
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
842
0
                pProgressData);
843
0
            break;
844
0
        case GDT_Int32:
845
0
            ComparePixels<int32_t, uint32_t, false>(
846
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
847
0
                pProgressData);
848
0
            break;
849
0
        case GDT_UInt64:
850
0
            ComparePixels<uint64_t, uint64_t, false>(
851
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
852
0
                pProgressData);
853
0
            break;
854
0
        case GDT_Int64:
855
0
            ComparePixels<int64_t, uint64_t, false>(
856
0
                aosReport, bandId, poRefBand, poInputBand, eReqDT, pfnProgress,
857
0
                pProgressData);
858
0
            break;
859
0
        case GDT_Float16:
860
0
        case GDT_Float32:
861
0
            ComparePixels<float, float, false>(aosReport, bandId, poRefBand,
862
0
                                               poInputBand, GDT_Float32,
863
0
                                               pfnProgress, pProgressData);
864
0
            break;
865
0
        case GDT_Float64:
866
0
            ComparePixels<double, double, false>(aosReport, bandId, poRefBand,
867
0
                                                 poInputBand, eReqDT,
868
0
                                                 pfnProgress, pProgressData);
869
0
            break;
870
0
        case GDT_CInt16:
871
0
            ComparePixels<int16_t, float, true>(aosReport, bandId, poRefBand,
872
0
                                                poInputBand, eReqDT,
873
0
                                                pfnProgress, pProgressData);
874
0
            break;
875
0
        case GDT_CInt32:
876
0
            ComparePixels<int32_t, double, true>(aosReport, bandId, poRefBand,
877
0
                                                 poInputBand, eReqDT,
878
0
                                                 pfnProgress, pProgressData);
879
0
            break;
880
0
        case GDT_CFloat16:
881
0
        case GDT_CFloat32:
882
0
            ComparePixels<float, float, true>(aosReport, bandId, poRefBand,
883
0
                                              poInputBand, GDT_CFloat32,
884
0
                                              pfnProgress, pProgressData);
885
0
            break;
886
0
        case GDT_CFloat64:
887
0
            ComparePixels<double, double, true>(aosReport, bandId, poRefBand,
888
0
                                                poInputBand, eReqDT,
889
0
                                                pfnProgress, pProgressData);
890
0
            break;
891
0
        case GDT_Unknown:
892
0
        case GDT_TypeCount:
893
0
            break;
894
0
    }
895
0
}
896
897
#if defined(__GNUC__) && !defined(__clang__)
898
#pragma GCC pop_options
899
#endif
900
901
/************************************************************************/
902
/*             GDALRasterCompareAlgorithm::BandComparison()             */
903
/************************************************************************/
904
905
void GDALRasterCompareAlgorithm::BandComparison(
906
    std::vector<std::string> &aosReport, const std::string &bandId,
907
    bool doBandBasedPixelComparison, GDALRasterBand *poRefBand,
908
    GDALRasterBand *poInputBand, GDALProgressFunc pfnProgress,
909
    void *pProgressData)
910
0
{
911
0
    bool ret = true;
912
913
0
    if (poRefBand->GetXSize() != poInputBand->GetXSize())
914
0
    {
915
0
        aosReport.push_back("Reference band width is " +
916
0
                            std::to_string(poRefBand->GetXSize()) +
917
0
                            ", but input band width is " +
918
0
                            std::to_string(poInputBand->GetXSize()));
919
0
        ret = false;
920
0
    }
921
922
0
    if (poRefBand->GetYSize() != poInputBand->GetYSize())
923
0
    {
924
0
        aosReport.push_back("Reference band height is " +
925
0
                            std::to_string(poRefBand->GetYSize()) +
926
0
                            ", but input band height is " +
927
0
                            std::to_string(poInputBand->GetYSize()));
928
0
        ret = false;
929
0
    }
930
931
0
    if (strcmp(poRefBand->GetDescription(), poInputBand->GetDescription()) != 0)
932
0
    {
933
0
        aosReport.push_back("Reference band " + bandId + " has description " +
934
0
                            std::string(poRefBand->GetDescription()) +
935
0
                            ", but input band has description " +
936
0
                            std::string(poInputBand->GetDescription()));
937
0
    }
938
939
0
    if (poRefBand->GetRasterDataType() != poInputBand->GetRasterDataType())
940
0
    {
941
0
        aosReport.push_back(
942
0
            "Reference band " + bandId + " has data type " +
943
0
            std::string(GDALGetDataTypeName(poRefBand->GetRasterDataType())) +
944
0
            ", but input band has data type " +
945
0
            std::string(GDALGetDataTypeName(poInputBand->GetRasterDataType())));
946
0
    }
947
948
0
    int bRefHasNoData = false;
949
0
    const double dfRefNoData = poRefBand->GetNoDataValue(&bRefHasNoData);
950
0
    int bInputHasNoData = false;
951
0
    const double dfInputNoData = poInputBand->GetNoDataValue(&bInputHasNoData);
952
0
    if (!bRefHasNoData && !bInputHasNoData)
953
0
    {
954
        // ok
955
0
    }
956
0
    else if (bRefHasNoData && !bInputHasNoData)
957
0
    {
958
0
        aosReport.push_back("Reference band " + bandId + " has nodata value " +
959
0
                            std::to_string(dfRefNoData) +
960
0
                            ", but input band has none.");
961
0
    }
962
0
    else if (!bRefHasNoData && bInputHasNoData)
963
0
    {
964
0
        aosReport.push_back("Reference band " + bandId +
965
0
                            " has no nodata value, " +
966
0
                            "but input band has no data value " +
967
0
                            std::to_string(dfInputNoData) + ".");
968
0
    }
969
0
    else if ((std::isnan(dfRefNoData) && std::isnan(dfInputNoData)) ||
970
0
             dfRefNoData == dfInputNoData)
971
0
    {
972
        // ok
973
0
    }
974
0
    else
975
0
    {
976
0
        aosReport.push_back("Reference band " + bandId + " has nodata value " +
977
0
                            std::to_string(dfRefNoData) +
978
0
                            ", but input band has no data value " +
979
0
                            std::to_string(dfInputNoData) + ".");
980
0
    }
981
982
0
    if (poRefBand->GetColorInterpretation() !=
983
0
        poInputBand->GetColorInterpretation())
984
0
    {
985
0
        aosReport.push_back("Reference band " + bandId +
986
0
                            " has color interpretation " +
987
0
                            std::string(GDALGetColorInterpretationName(
988
0
                                poRefBand->GetColorInterpretation())) +
989
0
                            ", but input band has color interpretation " +
990
0
                            std::string(GDALGetColorInterpretationName(
991
0
                                poInputBand->GetColorInterpretation())));
992
0
    }
993
994
0
    if (!ret)
995
0
        return;
996
997
0
    const uint64_t nBasePixels =
998
0
        static_cast<uint64_t>(poRefBand->GetXSize()) * poRefBand->GetYSize();
999
0
    uint64_t nTotalPixels = nBasePixels;
1000
0
    const int nOvrCount = poRefBand->GetOverviewCount();
1001
0
    if (!m_skipOverview && nOvrCount == poInputBand->GetOverviewCount())
1002
0
    {
1003
0
        for (int i = 0; i < nOvrCount; ++i)
1004
0
        {
1005
0
            auto poOvrBand = poRefBand->GetOverview(i);
1006
0
            const uint64_t nOvrPixels =
1007
0
                static_cast<uint64_t>(poOvrBand->GetXSize()) *
1008
0
                poOvrBand->GetYSize();
1009
0
            nTotalPixels += nOvrPixels;
1010
0
        }
1011
0
    }
1012
1013
0
    int nCountMetrics = 0;
1014
0
    if (doBandBasedPixelComparison)
1015
0
    {
1016
0
        if (HasMetric(METRIC_DIFF))
1017
0
            ++nCountMetrics;
1018
0
        if (HasMetric(METRIC_RMSD) || HasMetric(METRIC_PSNR))
1019
0
            ++nCountMetrics;
1020
0
    }
1021
1022
0
    double dfLastPct = 0;
1023
0
    if (doBandBasedPixelComparison && HasMetric(METRIC_DIFF))
1024
0
    {
1025
0
        double dfNewLastPct =
1026
0
            dfLastPct + static_cast<double>(nBasePixels) /
1027
0
                            static_cast<double>(nTotalPixels * nCountMetrics);
1028
0
        std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
1029
0
            pScaledProgress(GDALCreateScaledProgress(dfLastPct, dfNewLastPct,
1030
0
                                                     pfnProgress,
1031
0
                                                     pProgressData),
1032
0
                            GDALDestroyScaledProgress);
1033
0
        dfLastPct = dfNewLastPct;
1034
0
        ComparePixels(aosReport, bandId, poRefBand, poInputBand,
1035
0
                      pScaledProgress ? GDALScaledProgress : nullptr,
1036
0
                      pScaledProgress.get());
1037
0
    }
1038
1039
0
    if (doBandBasedPixelComparison &&
1040
0
        (HasMetric(METRIC_RMSD) || HasMetric(METRIC_PSNR)))
1041
0
    {
1042
        // For PSNR on floating point image, we need to compute min and max of
1043
        // reference band
1044
0
        const bool bIsInteger =
1045
0
            CPL_TO_BOOL(GDALDataTypeIsInteger(poRefBand->GetRasterDataType()));
1046
0
        const double dfScalingProgress =
1047
0
            HasMetric(METRIC_PSNR) && !bIsInteger ? 0.5 : 1;
1048
0
        double dfNewLastPct =
1049
0
            dfLastPct + dfScalingProgress * static_cast<double>(nBasePixels) /
1050
0
                            static_cast<double>(nTotalPixels * nCountMetrics);
1051
0
        std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
1052
0
            pScaledProgress(GDALCreateScaledProgress(dfLastPct, dfNewLastPct,
1053
0
                                                     pfnProgress,
1054
0
                                                     pProgressData),
1055
0
                            GDALDestroyScaledProgress);
1056
0
        dfLastPct = dfNewLastPct;
1057
1058
0
        auto diffBand = (*poRefBand) - (*poInputBand);
1059
0
        auto squaredDiffBand = diffBand * diffBand;
1060
0
        double dfMeanSquareError = 0;
1061
0
        if (squaredDiffBand.ComputeStatistics(
1062
0
                /* bApproxOK = */ false,
1063
0
                /* pdfMin = */ nullptr,
1064
0
                /* pdfMax = */ nullptr, &dfMeanSquareError,
1065
0
                /* pdfStdDev = */ nullptr,
1066
0
                pScaledProgress ? GDALScaledProgress : nullptr,
1067
0
                pScaledProgress.get(), nullptr) == CE_None)
1068
0
        {
1069
0
            const double dfRMSD = std::sqrt(dfMeanSquareError);
1070
0
            if (dfRMSD > 0)
1071
0
            {
1072
0
                if (HasMetric(METRIC_RMSD))
1073
0
                {
1074
0
                    aosReport.push_back(CPLSPrintf("Band %s: RMSD: %g",
1075
0
                                                   bandId.c_str(), dfRMSD));
1076
0
                }
1077
1078
0
                if (HasMetric(METRIC_PSNR))
1079
0
                {
1080
0
                    if (bIsInteger)
1081
0
                    {
1082
0
                        double dfMaxAmplitude;
1083
0
                        const char *pszNBITS = poRefBand->GetMetadataItem(
1084
0
                            GDALMD_NBITS, GDAL_MDD_IMAGE_STRUCTURE);
1085
0
                        if (pszNBITS)
1086
0
                            dfMaxAmplitude = std::pow(2.0, atoi(pszNBITS)) - 1;
1087
0
                        else
1088
0
                            dfMaxAmplitude =
1089
0
                                std::pow(2.0,
1090
0
                                         GDALGetDataTypeSizeBits(
1091
0
                                             poRefBand->GetRasterDataType())) -
1092
0
                                1;
1093
1094
0
                        const double dfPSNR_dB =
1095
0
                            20 * std::log10(dfMaxAmplitude / dfRMSD);
1096
0
                        aosReport.push_back(CPLSPrintf("Band %s: PSNR (dB): %g",
1097
0
                                                       bandId.c_str(),
1098
0
                                                       dfPSNR_dB));
1099
0
                    }
1100
0
                    else
1101
0
                    {
1102
0
                        dfNewLastPct =
1103
0
                            dfLastPct + dfScalingProgress *
1104
0
                                            static_cast<double>(nBasePixels) /
1105
0
                                            static_cast<double>(nTotalPixels *
1106
0
                                                                nCountMetrics);
1107
0
                        pScaledProgress.reset(GDALCreateScaledProgress(
1108
0
                            dfLastPct, dfNewLastPct, pfnProgress,
1109
0
                            pProgressData));
1110
0
                        dfLastPct = dfNewLastPct;
1111
0
                        double dfMin = 0;
1112
0
                        double dfMax = 0;
1113
0
                        const char *const apszOptions[] = {
1114
0
                            "SET_STATISTICS=FALSE", nullptr};
1115
0
                        if (poRefBand->ComputeStatistics(
1116
0
                                /* bApproxOK = */ false, &dfMin, &dfMax,
1117
0
                                nullptr, nullptr,
1118
0
                                pScaledProgress ? GDALScaledProgress : nullptr,
1119
0
                                pScaledProgress.get(), apszOptions) == CE_None)
1120
0
                        {
1121
0
                            const double dfPSNR_dB =
1122
0
                                20 * std::log10((dfMax - dfMin) / dfRMSD);
1123
0
                            aosReport.push_back(
1124
0
                                CPLSPrintf("Band %s: PSNR (dB): %g",
1125
0
                                           bandId.c_str(), dfPSNR_dB));
1126
0
                        }
1127
0
                        else
1128
0
                        {
1129
0
                            aosReport.push_back(
1130
0
                                std::string("Error during PSNR computation: ")
1131
0
                                    .append(CPLGetLastErrorMsg()));
1132
0
                        }
1133
0
                    }
1134
0
                }
1135
0
            }
1136
0
        }
1137
0
        else
1138
0
        {
1139
0
            aosReport.push_back(
1140
0
                std::string("Error during RMSD/PSNR computation: ")
1141
0
                    .append(CPLGetLastErrorMsg()));
1142
0
        }
1143
0
    }
1144
1145
0
    CPL_IGNORE_RET_VAL(dfLastPct);
1146
1147
0
    if (!m_skipOverview)
1148
0
    {
1149
0
        if (nOvrCount != poInputBand->GetOverviewCount())
1150
0
        {
1151
0
            aosReport.push_back(
1152
0
                "Reference band " + bandId + " has " +
1153
0
                std::to_string(nOvrCount) +
1154
0
                " overview band(s), but input band has " +
1155
0
                std::to_string(poInputBand->GetOverviewCount()));
1156
0
        }
1157
0
        else
1158
0
        {
1159
0
            uint64_t nIterPixels = nBasePixels;
1160
1161
0
            for (int i = 0; i < nOvrCount; ++i)
1162
0
            {
1163
0
                GDALRasterBand *poOvrBand = poRefBand->GetOverview(i);
1164
0
                const uint64_t nOvrPixels =
1165
0
                    static_cast<uint64_t>(poOvrBand->GetXSize()) *
1166
0
                    poOvrBand->GetYSize();
1167
0
                void *pScaledProgress = GDALCreateScaledProgress(
1168
0
                    static_cast<double>(nIterPixels) /
1169
0
                        static_cast<double>(nTotalPixels),
1170
0
                    static_cast<double>(nIterPixels + nOvrPixels) /
1171
0
                        static_cast<double>(nTotalPixels),
1172
0
                    pfnProgress, pProgressData);
1173
0
                BandComparison(aosReport, "overview of band " + bandId,
1174
0
                               doBandBasedPixelComparison, poOvrBand,
1175
0
                               poInputBand->GetOverview(i),
1176
0
                               pScaledProgress ? GDALScaledProgress : nullptr,
1177
0
                               pScaledProgress);
1178
0
                GDALDestroyScaledProgress(pScaledProgress);
1179
0
                nIterPixels += nOvrPixels;
1180
0
            }
1181
0
        }
1182
0
    }
1183
1184
0
    if (poRefBand->GetMaskFlags() != poInputBand->GetMaskFlags())
1185
0
    {
1186
0
        aosReport.push_back("Reference band " + bandId + " has mask flags = " +
1187
0
                            std::to_string(poRefBand->GetMaskFlags()) +
1188
0
                            " , but input band has mask flags = " +
1189
0
                            std::to_string(poInputBand->GetMaskFlags()));
1190
0
    }
1191
0
    else if (poRefBand->GetMaskFlags() == GMF_PER_DATASET)
1192
0
    {
1193
0
        BandComparison(aosReport, "mask of band " + bandId, true,
1194
0
                       poRefBand->GetMaskBand(), poInputBand->GetMaskBand(),
1195
0
                       nullptr, nullptr);
1196
0
    }
1197
1198
0
    if (!m_skipMetadata)
1199
0
    {
1200
0
        MetadataComparison(aosReport, "(band default metadata domain)",
1201
0
                           poRefBand->GetMetadata(),
1202
0
                           poInputBand->GetMetadata());
1203
0
    }
1204
0
}
1205
1206
/************************************************************************/
1207
/*           GDALRasterCompareAlgorithm::MetadataComparison()           */
1208
/************************************************************************/
1209
1210
void GDALRasterCompareAlgorithm::MetadataComparison(
1211
    std::vector<std::string> &aosReport, const std::string &metadataDomain,
1212
    CSLConstList aosRef, CSLConstList aosInput)
1213
0
{
1214
0
    std::map<std::string, std::string> oMapRef;
1215
0
    std::map<std::string, std::string> oMapInput;
1216
1217
0
    std::array<const char *, 3> ignoredKeys = {
1218
0
        "backend",   // from gdalcompare.py. Not sure why
1219
0
        "ERR_BIAS",  // RPC optional key
1220
0
        "ERR_RAND",  // RPC optional key
1221
0
    };
1222
1223
0
    for (const auto &[key, value] : cpl::IterateNameValue(aosRef))
1224
0
    {
1225
0
        const char *pszKey = key;
1226
0
        const auto eq = [pszKey](const char *s)
1227
0
        { return strcmp(pszKey, s) == 0; };
1228
0
        auto it = std::find_if(ignoredKeys.begin(), ignoredKeys.end(), eq);
1229
0
        if (it == ignoredKeys.end())
1230
0
        {
1231
0
            oMapRef[key] = value;
1232
0
        }
1233
0
    }
1234
1235
0
    for (const auto &[key, value] : cpl::IterateNameValue(aosInput))
1236
0
    {
1237
0
        const char *pszKey = key;
1238
0
        const auto eq = [pszKey](const char *s)
1239
0
        { return strcmp(pszKey, s) == 0; };
1240
0
        auto it = std::find_if(ignoredKeys.begin(), ignoredKeys.end(), eq);
1241
0
        if (it == ignoredKeys.end())
1242
0
        {
1243
0
            oMapInput[key] = value;
1244
0
        }
1245
0
    }
1246
1247
0
    const auto strip = [](const std::string &s)
1248
0
    {
1249
0
        const auto posBegin = s.find_first_not_of(' ');
1250
0
        if (posBegin == std::string::npos)
1251
0
            return std::string();
1252
0
        const auto posEnd = s.find_last_not_of(' ');
1253
0
        return s.substr(posBegin, posEnd - posBegin + 1);
1254
0
    };
1255
1256
0
    for (const auto &sKeyValuePair : oMapRef)
1257
0
    {
1258
0
        const auto oIter = oMapInput.find(sKeyValuePair.first);
1259
0
        if (oIter == oMapInput.end())
1260
0
        {
1261
0
            aosReport.push_back("Reference metadata " + metadataDomain +
1262
0
                                " contains key '" + sKeyValuePair.first +
1263
0
                                "' but input metadata does not.");
1264
0
        }
1265
0
        else
1266
0
        {
1267
            // this will always have the current date set
1268
0
            if (sKeyValuePair.first == "NITF_FDT")
1269
0
                continue;
1270
1271
0
            std::string ref = oIter->second;
1272
0
            std::string input = sKeyValuePair.second;
1273
0
            if (metadataDomain == GDAL_MDD_RPC)
1274
0
            {
1275
                // _RPC.TXT files and in-file have a difference
1276
                // in white space that is not otherwise meaningful.
1277
0
                ref = strip(ref);
1278
0
                input = strip(input);
1279
0
            }
1280
0
            if (ref != input)
1281
0
            {
1282
0
                aosReport.push_back(
1283
0
                    "Reference metadata " + metadataDomain + " has value '" +
1284
0
                    ref + "' for key '" + sKeyValuePair.first +
1285
0
                    "' but input metadata has value '" + input + "'.");
1286
0
            }
1287
0
        }
1288
0
    }
1289
1290
0
    for (const auto &sKeyValuePair : oMapInput)
1291
0
    {
1292
0
        if (!cpl::contains(oMapRef, sKeyValuePair.first))
1293
0
        {
1294
0
            aosReport.push_back("Input metadata " + metadataDomain +
1295
0
                                " contains key '" + sKeyValuePair.first +
1296
0
                                "' but reference metadata does not.");
1297
0
        }
1298
0
    }
1299
0
}
1300
1301
/************************************************************************/
1302
/*                GDALRasterCompareAlgorithm::RunStep()                 */
1303
/************************************************************************/
1304
1305
bool GDALRasterCompareAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
1306
0
{
1307
0
    auto poRefDS = m_referenceDataset.GetDatasetRef();
1308
0
    CPLAssert(poRefDS);
1309
1310
0
    CPLAssert(m_inputDataset.size() == 1);
1311
0
    auto poInputDS = m_inputDataset[0].GetDatasetRef();
1312
0
    CPLAssert(poInputDS);
1313
1314
    if constexpr (!HAVE_MUPARSER)
1315
0
    {
1316
0
        for (const char *pszMetric : {METRIC_RMSD, METRIC_PSNR})
1317
0
        {
1318
0
            if (HasMetric(pszMetric))
1319
0
            {
1320
0
                CPLError(CE_Failure, CPLE_NotSupported,
1321
0
                         "%s metric not supported in a GDAL build without "
1322
0
                         "MuParser support",
1323
0
                         pszMetric);
1324
0
                return false;
1325
0
            }
1326
0
        }
1327
0
    }
1328
1329
0
    if (m_skipAllOptional)
1330
0
    {
1331
0
        m_skipBinary = true;
1332
0
        m_skipCRS = true;
1333
0
        m_skipGeotransform = true;
1334
0
        m_skipOverview = true;
1335
0
        m_skipMetadata = true;
1336
0
        m_skipRPC = true;
1337
0
        m_skipGeolocation = true;
1338
0
        m_skipSubdataset = true;
1339
0
    }
1340
1341
0
    std::vector<std::string> aosReport;
1342
1343
0
    if (!m_skipBinary)
1344
0
    {
1345
0
        if (BinaryComparison(this, aosReport, poRefDS, poInputDS))
1346
0
        {
1347
0
            return true;
1348
0
        }
1349
0
    }
1350
1351
0
    CSLConstList papszSubDSRef =
1352
0
        m_skipSubdataset ? nullptr : poRefDS->GetMetadata(GDAL_MDD_SUBDATASETS);
1353
0
    const int nCountRef = CSLCount(papszSubDSRef) / 2;
1354
0
    CSLConstList papszSubDSInput =
1355
0
        m_skipSubdataset ? nullptr
1356
0
                         : poInputDS->GetMetadata(GDAL_MDD_SUBDATASETS);
1357
0
    const int nCountInput = CSLCount(papszSubDSInput) / 2;
1358
1359
0
    if (!m_skipSubdataset)
1360
0
    {
1361
0
        if (nCountRef != nCountInput)
1362
0
        {
1363
0
            aosReport.push_back("Reference dataset has " +
1364
0
                                std::to_string(nCountRef) +
1365
0
                                " subdataset(s) whereas input dataset has " +
1366
0
                                std::to_string(nCountInput) + " one(s).");
1367
0
            m_skipSubdataset = true;
1368
0
        }
1369
0
    }
1370
1371
    // Compute total number of pixels, including in subdatasets
1372
0
    const uint64_t nBasePixels =
1373
0
        static_cast<uint64_t>(poRefDS->GetRasterXSize()) *
1374
0
        poRefDS->GetRasterYSize() * poRefDS->GetRasterCount();
1375
0
    uint64_t nTotalPixels = nBasePixels;
1376
0
    if (ctxt.m_pfnProgress && !m_skipSubdataset)
1377
0
    {
1378
0
        for (int i = 0; i < nCountRef; ++i)
1379
0
        {
1380
0
            const char *pszRef = CSLFetchNameValue(
1381
0
                papszSubDSRef, CPLSPrintf("SUBDATASET_%d_NAME", i + 1));
1382
0
            const char *pszInput = CSLFetchNameValue(
1383
0
                papszSubDSInput, CPLSPrintf("SUBDATASET_%d_NAME", i + 1));
1384
0
            if (pszRef && pszInput)
1385
0
            {
1386
0
                auto poSubRef = std::unique_ptr<GDALDataset>(
1387
0
                    GDALDataset::Open(pszRef, GDAL_OF_RASTER));
1388
0
                auto poSubInput = std::unique_ptr<GDALDataset>(
1389
0
                    GDALDataset::Open(pszInput, GDAL_OF_RASTER));
1390
0
                if (poSubRef && poSubInput)
1391
0
                {
1392
0
                    const uint64_t nSubDSPixels =
1393
0
                        static_cast<uint64_t>(poSubRef->GetRasterXSize()) *
1394
0
                        poSubRef->GetRasterYSize() * poSubRef->GetRasterCount();
1395
0
                    nTotalPixels += nSubDSPixels;
1396
0
                }
1397
0
            }
1398
0
        }
1399
0
    }
1400
1401
0
    {
1402
0
        void *pScaledProgress =
1403
0
            GDALCreateScaledProgress(0.0,
1404
0
                                     static_cast<double>(nBasePixels) /
1405
0
                                         static_cast<double>(nTotalPixels),
1406
0
                                     ctxt.m_pfnProgress, ctxt.m_pProgressData);
1407
0
        DatasetComparison(aosReport, poRefDS, poInputDS,
1408
0
                          pScaledProgress ? GDALScaledProgress : nullptr,
1409
0
                          pScaledProgress);
1410
0
        GDALDestroyScaledProgress(pScaledProgress);
1411
0
    }
1412
1413
0
    if (!m_skipSubdataset)
1414
0
    {
1415
0
        uint64_t nIterPixels = nBasePixels;
1416
0
        for (int i = 0; i < nCountRef; ++i)
1417
0
        {
1418
0
            const char *pszRef = CSLFetchNameValue(
1419
0
                papszSubDSRef, CPLSPrintf("SUBDATASET_%d_NAME", i + 1));
1420
0
            const char *pszInput = CSLFetchNameValue(
1421
0
                papszSubDSInput, CPLSPrintf("SUBDATASET_%d_NAME", i + 1));
1422
0
            if (pszRef && pszInput)
1423
0
            {
1424
0
                auto poSubRef = std::unique_ptr<GDALDataset>(GDALDataset::Open(
1425
0
                    pszRef, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
1426
0
                auto poSubInput =
1427
0
                    std::unique_ptr<GDALDataset>(GDALDataset::Open(
1428
0
                        pszInput, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
1429
0
                if (poSubRef && poSubInput)
1430
0
                {
1431
0
                    const uint64_t nSubDSPixels =
1432
0
                        static_cast<uint64_t>(poSubRef->GetRasterXSize()) *
1433
0
                        poSubRef->GetRasterYSize() * poSubRef->GetRasterCount();
1434
0
                    void *pScaledProgress = GDALCreateScaledProgress(
1435
0
                        static_cast<double>(nIterPixels) /
1436
0
                            static_cast<double>(nTotalPixels),
1437
0
                        static_cast<double>(nIterPixels + nSubDSPixels) /
1438
0
                            static_cast<double>(nTotalPixels),
1439
0
                        ctxt.m_pfnProgress, ctxt.m_pProgressData);
1440
0
                    DatasetComparison(
1441
0
                        aosReport, poSubRef.get(), poSubInput.get(),
1442
0
                        pScaledProgress ? GDALScaledProgress : nullptr,
1443
0
                        pScaledProgress);
1444
0
                    GDALDestroyScaledProgress(pScaledProgress);
1445
0
                    nIterPixels += nSubDSPixels;
1446
0
                }
1447
0
            }
1448
0
        }
1449
0
    }
1450
1451
0
    for (const auto &s : aosReport)
1452
0
    {
1453
0
        m_output += s;
1454
0
        m_output += '\n';
1455
0
    }
1456
1457
0
    m_retCode = static_cast<int>(aosReport.size());
1458
1459
0
    return true;
1460
0
}
1461
1462
/************************************************************************/
1463
/*               ~GDALRasterCompareAlgorithmStandalone()                */
1464
/************************************************************************/
1465
1466
0
GDALRasterCompareAlgorithmStandalone::~GDALRasterCompareAlgorithmStandalone() =
1467
    default;
1468
1469
//! @endcond