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

Generated by: LCOV version 1.14