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

Generated by: LCOV version 1.14