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

Generated by: LCOV version 1.14