LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_compare.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 615 632 97.3 %
Date: 2026-06-03 12:46:18 Functions: 69 69 100.0 %

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

Generated by: LCOV version 1.14