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

Generated by: LCOV version 1.14