/src/gdal/apps/gdalalg_mdim_compare.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: gdal "mdim compare" subcommand |
5 | | * Author: Even Rouault <even dot rouault at spatialys.com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "gdalalg_mdim_compare.h" |
14 | | |
15 | | #include "gdal_dataset.h" |
16 | | #include "gdal_multidim.h" |
17 | | |
18 | | #include <algorithm> |
19 | | #include <cinttypes> |
20 | | #include <cmath> |
21 | | #include <iterator> |
22 | | #include <limits> |
23 | | #include <set> |
24 | | #include <utility> |
25 | | |
26 | | //! @cond Doxygen_Suppress |
27 | | |
28 | | #ifndef _ |
29 | 0 | #define _(x) (x) |
30 | | #endif |
31 | | |
32 | | /************************************************************************/ |
33 | | /* GDALMdimCompareAlgorithm::GDALMdimCompareAlgorithm() */ |
34 | | /************************************************************************/ |
35 | | |
36 | | GDALMdimCompareAlgorithm::GDALMdimCompareAlgorithm(bool standaloneStep) |
37 | 0 | : GDALMdimPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL, |
38 | 0 | ConstructorOptions() |
39 | 0 | .SetStandaloneStep(standaloneStep) |
40 | 0 | .SetInputDatasetMaxCount(1) |
41 | 0 | .SetAddDefaultArguments(false)) |
42 | 0 | { |
43 | 0 | if (standaloneStep) |
44 | 0 | { |
45 | 0 | AddProgressArg(); |
46 | 0 | } |
47 | 0 | else |
48 | 0 | { |
49 | 0 | AddMdimHiddenInputDatasetArg(); |
50 | 0 | } |
51 | |
|
52 | 0 | auto &referenceDatasetArg = |
53 | 0 | AddArg("reference", 0, _("Reference dataset"), &m_referenceDataset, |
54 | 0 | GDAL_OF_MULTIDIM_RASTER) |
55 | 0 | .SetPositional() |
56 | 0 | .SetRequired(); |
57 | |
|
58 | 0 | SetAutoCompleteFunctionForFilename(referenceDatasetArg, |
59 | 0 | GDAL_OF_MULTIDIM_RASTER); |
60 | |
|
61 | 0 | if (standaloneStep) |
62 | 0 | { |
63 | 0 | AddMdimInputArgs(/* openForMixedMdimVector = */ false, |
64 | 0 | /* hiddenForCLI = */ false, |
65 | 0 | /* acceptRaster = */ false); |
66 | 0 | } |
67 | |
|
68 | 0 | AddArg("metric", 0, _("Comparison metric(s)"), &m_metrics) |
69 | 0 | .SetChoices(METRIC_ALL, METRIC_NONE, METRIC_DIFF, METRIC_RMSD, |
70 | 0 | METRIC_PSNR) |
71 | 0 | .SetDefault(METRIC_DEFAULT); |
72 | |
|
73 | 0 | AddArrayNameArg(&m_array, _("Name of array(s) to compare")); |
74 | |
|
75 | 0 | AddOutputStringArg(&m_output); |
76 | |
|
77 | 0 | AddArg("skip-binary", 0, _("Skip binary file comparison"), &m_skipBinary); |
78 | |
|
79 | 0 | AddArg("return-code", 0, _("Return code"), &m_retCode) |
80 | 0 | .SetHiddenForCLI() |
81 | 0 | .SetIsInput(false) |
82 | 0 | .SetIsOutput(true); |
83 | 0 | } |
84 | | |
85 | | /************************************************************************/ |
86 | | /* GetPixelCount() */ |
87 | | /************************************************************************/ |
88 | | |
89 | | static uint64_t GetPixelCount(const std::shared_ptr<GDALMDArray> &array) |
90 | 0 | { |
91 | 0 | uint64_t nPixels = 1; |
92 | 0 | for (const auto &poDim : array->GetDimensions()) |
93 | 0 | { |
94 | 0 | nPixels *= poDim->GetSize(); |
95 | 0 | } |
96 | 0 | return nPixels; |
97 | 0 | } |
98 | | |
99 | | /************************************************************************/ |
100 | | /* GDALMdimCompareAlgorithm::RunStep() */ |
101 | | /************************************************************************/ |
102 | | |
103 | | bool GDALMdimCompareAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt) |
104 | 0 | { |
105 | 0 | auto poRefDS = m_referenceDataset.GetDatasetRef(); |
106 | 0 | CPLAssert(poRefDS); |
107 | | |
108 | 0 | CPLAssert(m_inputDataset.size() == 1); |
109 | 0 | auto poInputDS = m_inputDataset[0].GetDatasetRef(); |
110 | 0 | CPLAssert(poInputDS); |
111 | | |
112 | 0 | std::vector<std::string> aosReport; |
113 | |
|
114 | 0 | if (!m_skipBinary) |
115 | 0 | { |
116 | 0 | if (BinaryComparison(this, aosReport, poRefDS, poInputDS)) |
117 | 0 | { |
118 | 0 | return true; |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | 0 | auto poRefRootGroup = poRefDS->GetRootGroup(); |
123 | 0 | if (!poRefRootGroup) |
124 | 0 | { |
125 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
126 | 0 | "Cannot get root group on reference dataset"); |
127 | 0 | return false; |
128 | 0 | } |
129 | | |
130 | 0 | auto poInputRootGroup = poInputDS->GetRootGroup(); |
131 | 0 | if (!poInputRootGroup) |
132 | 0 | { |
133 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
134 | 0 | "Cannot get root group on input dataset"); |
135 | 0 | return false; |
136 | 0 | } |
137 | | |
138 | 0 | auto refArrays = poRefRootGroup->GetMDArrayFullNamesRecursive(); |
139 | 0 | auto inputArrays = poInputRootGroup->GetMDArrayFullNamesRecursive(); |
140 | 0 | if (m_array.empty()) |
141 | 0 | { |
142 | 0 | std::sort(refArrays.begin(), refArrays.end()); |
143 | 0 | std::sort(inputArrays.begin(), inputArrays.end()); |
144 | |
|
145 | 0 | { |
146 | 0 | std::vector<std::string> missing; |
147 | 0 | std::set_difference(refArrays.begin(), refArrays.end(), |
148 | 0 | inputArrays.begin(), inputArrays.end(), |
149 | 0 | std::back_inserter(missing)); |
150 | 0 | if (!missing.empty()) |
151 | 0 | { |
152 | 0 | std::string line = |
153 | 0 | "The following arrays are found in the reference dataset, " |
154 | 0 | "but missing in the input one: "; |
155 | 0 | bool first = true; |
156 | 0 | for (const auto &name : missing) |
157 | 0 | { |
158 | 0 | if (!first) |
159 | 0 | line += ", "; |
160 | 0 | first = false; |
161 | 0 | line += name; |
162 | 0 | } |
163 | 0 | aosReport.push_back(std::move(line)); |
164 | 0 | } |
165 | 0 | } |
166 | |
|
167 | 0 | { |
168 | 0 | std::vector<std::string> missing; |
169 | 0 | std::set_difference(inputArrays.begin(), inputArrays.end(), |
170 | 0 | refArrays.begin(), refArrays.end(), |
171 | 0 | std::back_inserter(missing)); |
172 | 0 | if (!missing.empty()) |
173 | 0 | { |
174 | 0 | std::string line = |
175 | 0 | "The following arrays are found in the input dataset, but " |
176 | 0 | "missing in the reference one: "; |
177 | 0 | bool first = true; |
178 | 0 | for (const auto &name : missing) |
179 | 0 | { |
180 | 0 | if (!first) |
181 | 0 | line += ", "; |
182 | 0 | first = false; |
183 | 0 | line += name; |
184 | 0 | } |
185 | 0 | aosReport.push_back(std::move(line)); |
186 | 0 | } |
187 | 0 | } |
188 | |
|
189 | 0 | std::set_intersection(refArrays.begin(), refArrays.end(), |
190 | 0 | inputArrays.begin(), inputArrays.end(), |
191 | 0 | std::back_inserter(m_array)); |
192 | 0 | } |
193 | 0 | else |
194 | 0 | { |
195 | 0 | std::set<std::string> newArrays; |
196 | 0 | for (const auto &name : m_array) |
197 | 0 | { |
198 | 0 | const auto ExistsIn = |
199 | 0 | [&name, |
200 | 0 | &newArrays](const std::vector<std::string> &aosExitingArrays, |
201 | 0 | bool insert) |
202 | 0 | { |
203 | 0 | bool ret = false; |
204 | 0 | for (const auto &exitingArray : aosExitingArrays) |
205 | 0 | { |
206 | 0 | if (!name.empty() && |
207 | 0 | ((name[0] == '/' && name == exitingArray) || |
208 | 0 | (name[0] != '/' && |
209 | 0 | name == CPLGetFilename(exitingArray.c_str())))) |
210 | 0 | { |
211 | 0 | if (insert) |
212 | 0 | newArrays.insert(exitingArray); |
213 | 0 | ret = true; |
214 | 0 | } |
215 | 0 | } |
216 | 0 | return ret; |
217 | 0 | }; |
218 | |
|
219 | 0 | if (!ExistsIn(refArrays, true)) |
220 | 0 | { |
221 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
222 | 0 | "Array '%s' does not exist in reference dataset", |
223 | 0 | name.c_str()); |
224 | 0 | return false; |
225 | 0 | } |
226 | 0 | if (!ExistsIn(inputArrays, false)) |
227 | 0 | { |
228 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
229 | 0 | "Array '%s' does not exist in input dataset", |
230 | 0 | name.c_str()); |
231 | 0 | return false; |
232 | 0 | } |
233 | 0 | } |
234 | 0 | m_array.clear(); |
235 | 0 | m_array.insert(m_array.end(), newArrays.begin(), newArrays.end()); |
236 | 0 | } |
237 | | |
238 | 0 | std::vector< |
239 | 0 | std::pair<std::shared_ptr<GDALMDArray>, std::shared_ptr<GDALMDArray>>> |
240 | 0 | arrayPairs; |
241 | | // not zero, to avoid false positive Coverity Scan warning |
242 | 0 | double dfTotalPixels = std::numeric_limits<double>::min(); |
243 | 0 | for (const auto &array : m_array) |
244 | 0 | { |
245 | 0 | auto poRefArray = poRefRootGroup->OpenMDArrayFromFullname(array); |
246 | 0 | if (!poRefArray) |
247 | 0 | { |
248 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
249 | 0 | "Array '%s' cannot be opened in reference dataset", |
250 | 0 | array.c_str()); |
251 | 0 | return false; |
252 | 0 | } |
253 | | |
254 | 0 | auto poInputArray = poInputRootGroup->OpenMDArrayFromFullname(array); |
255 | 0 | if (!poInputArray) |
256 | 0 | { |
257 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
258 | 0 | "Array '%s' cannot be opened in input dataset", |
259 | 0 | array.c_str()); |
260 | 0 | return false; |
261 | 0 | } |
262 | | |
263 | 0 | dfTotalPixels += static_cast<double>(GetPixelCount(poRefArray)); |
264 | 0 | arrayPairs.emplace_back(std::move(poRefArray), std::move(poInputArray)); |
265 | 0 | } |
266 | | |
267 | 0 | uint64_t nCurPixels = 0; |
268 | 0 | for (const auto &[poRefArray, poInputArray] : arrayPairs) |
269 | 0 | { |
270 | 0 | const uint64_t nThisArrayPixels = GetPixelCount(poRefArray); |
271 | 0 | const double dfMinPct = static_cast<double>(nCurPixels) / dfTotalPixels; |
272 | 0 | const double dfMaxPct = |
273 | 0 | static_cast<double>(nCurPixels + nThisArrayPixels) / dfTotalPixels; |
274 | 0 | std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> |
275 | 0 | pScaledProgress(GDALCreateScaledProgress(dfMinPct, dfMaxPct, |
276 | 0 | ctxt.m_pfnProgress, |
277 | 0 | ctxt.m_pProgressData), |
278 | 0 | GDALDestroyScaledProgress); |
279 | 0 | CompareArray(aosReport, poRefArray, poInputArray, |
280 | 0 | pScaledProgress ? GDALScaledProgress : nullptr, |
281 | 0 | pScaledProgress.get()); |
282 | 0 | nCurPixels += nThisArrayPixels; |
283 | 0 | } |
284 | |
|
285 | 0 | if (ctxt.m_pfnProgress) |
286 | 0 | ctxt.m_pfnProgress(1.0, "", ctxt.m_pProgressData); |
287 | |
|
288 | 0 | for (const auto &s : aosReport) |
289 | 0 | { |
290 | 0 | m_output += s; |
291 | 0 | m_output += '\n'; |
292 | 0 | } |
293 | |
|
294 | 0 | m_retCode = static_cast<int>(aosReport.size()); |
295 | |
|
296 | 0 | return true; |
297 | 0 | } |
298 | | |
299 | | /************************************************************************/ |
300 | | /* NonZeroValueIterator */ |
301 | | /************************************************************************/ |
302 | | |
303 | | namespace |
304 | | { |
305 | | struct NonZeroValueIterator |
306 | | { |
307 | | std::vector<double> adfValues{}; |
308 | | uint64_t nNonZero = 0; |
309 | | |
310 | | static bool func(GDALAbstractMDArray *array, |
311 | | const GUInt64 *chunkArrayStartIdx, |
312 | | const size_t *chunkCount, GUInt64 /* iCurChunk */, |
313 | | GUInt64 /* nChunkCount */, void *pUserData) |
314 | 0 | { |
315 | 0 | auto self = static_cast<NonZeroValueIterator *>(pUserData); |
316 | 0 | const size_t nDims = array->GetDimensionCount(); |
317 | 0 | size_t nElts = 1; |
318 | 0 | for (size_t i = 0; i < nDims; ++i) |
319 | 0 | nElts *= chunkCount[i]; |
320 | 0 | if (self->adfValues.size() < nElts) |
321 | 0 | { |
322 | 0 | try |
323 | 0 | { |
324 | 0 | self->adfValues.resize(nElts); |
325 | 0 | } |
326 | 0 | catch (const std::exception &) |
327 | 0 | { |
328 | 0 | CPLError(CE_Failure, CPLE_OutOfMemory, "Out of memory"); |
329 | 0 | return false; |
330 | 0 | } |
331 | 0 | } |
332 | 0 | if (!array->Read(chunkArrayStartIdx, chunkCount, |
333 | 0 | /* step = */ nullptr, /* stride = */ nullptr, |
334 | 0 | GDALExtendedDataType::Create(GDT_Float64), |
335 | 0 | self->adfValues.data())) |
336 | 0 | { |
337 | 0 | return false; |
338 | 0 | } |
339 | 0 | for (size_t i = 0; i < nElts; ++i) |
340 | 0 | { |
341 | 0 | if (self->adfValues[i] != 0) |
342 | 0 | self->nNonZero++; |
343 | 0 | } |
344 | |
|
345 | 0 | return true; |
346 | 0 | } |
347 | | }; |
348 | | } // namespace |
349 | | |
350 | | /************************************************************************/ |
351 | | /* GetDataTypeName() */ |
352 | | /************************************************************************/ |
353 | | |
354 | | static std::string GetDataTypeName(const GDALExtendedDataType &dt) |
355 | 0 | { |
356 | 0 | switch (dt.GetClass()) |
357 | 0 | { |
358 | 0 | case GEDTC_NUMERIC: |
359 | 0 | return GDALGetDataTypeName(dt.GetNumericDataType()); |
360 | 0 | case GEDTC_STRING: |
361 | 0 | return "String"; |
362 | 0 | case GEDTC_COMPOUND: |
363 | 0 | break; |
364 | 0 | } |
365 | 0 | return "Compound"; |
366 | 0 | } |
367 | | |
368 | | /************************************************************************/ |
369 | | /* GDALMdimCompareAlgorithm::CompareArray() */ |
370 | | /************************************************************************/ |
371 | | |
372 | | void GDALMdimCompareAlgorithm::CompareArray( |
373 | | std::vector<std::string> &aosReport, |
374 | | const std::shared_ptr<GDALMDArray> &poRefArray, |
375 | | const std::shared_ptr<GDALMDArray> &poInputArray, |
376 | | GDALProgressFunc pfnProgress, void *pProgressData) |
377 | 0 | { |
378 | 0 | const auto &osName = poRefArray->GetFullName(); |
379 | 0 | const auto nDims = poRefArray->GetDimensionCount(); |
380 | 0 | if (nDims != poInputArray->GetDimensionCount()) |
381 | 0 | { |
382 | 0 | aosReport.push_back( |
383 | 0 | CPLSPrintf("Array %s: dimension count in reference is %d, whereas " |
384 | 0 | "it is %d in input", |
385 | 0 | osName.c_str(), static_cast<int>(nDims), |
386 | 0 | static_cast<int>(poInputArray->GetDimensionCount()))); |
387 | 0 | return; |
388 | 0 | } |
389 | | |
390 | 0 | if (!poRefArray->HasSameShapeAs(*poInputArray)) |
391 | 0 | { |
392 | 0 | const auto ShapeToString = [](const std::shared_ptr<GDALMDArray> &array) |
393 | 0 | { |
394 | 0 | std::string s("["); |
395 | 0 | for (const auto &poDim : array->GetDimensions()) |
396 | 0 | { |
397 | 0 | if (s.size() > 1) |
398 | 0 | s += ','; |
399 | 0 | s += std::to_string(poDim->GetSize()); |
400 | 0 | } |
401 | 0 | s += ']'; |
402 | 0 | return s; |
403 | 0 | }; |
404 | |
|
405 | 0 | aosReport.push_back(CPLSPrintf( |
406 | 0 | "Array %s: shape in reference is %s, whereas it is %s in input", |
407 | 0 | osName.c_str(), ShapeToString(poRefArray).c_str(), |
408 | 0 | ShapeToString(poInputArray).c_str())); |
409 | 0 | return; |
410 | 0 | } |
411 | | |
412 | 0 | if (poRefArray->GetDataType() != poInputArray->GetDataType()) |
413 | 0 | { |
414 | 0 | aosReport.push_back(CPLSPrintf( |
415 | 0 | "Array %s: data type in reference is %s, whereas it is %s in input", |
416 | 0 | osName.c_str(), GetDataTypeName(poRefArray->GetDataType()).c_str(), |
417 | 0 | GetDataTypeName(poInputArray->GetDataType()).c_str())); |
418 | 0 | } |
419 | 0 | if (poRefArray->GetDataType().GetClass() != GEDTC_NUMERIC || |
420 | 0 | poInputArray->GetDataType().GetClass() != GEDTC_NUMERIC) |
421 | 0 | { |
422 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
423 | 0 | "Array %s: not compared, as comparison of non-numeric data " |
424 | 0 | "types is not currently supported", |
425 | 0 | osName.c_str()); |
426 | 0 | return; |
427 | 0 | } |
428 | | |
429 | 0 | auto diffArray = (*poRefArray) - poInputArray; |
430 | 0 | if (!diffArray) |
431 | 0 | { |
432 | | // Given above checks, this shouldn't happen. |
433 | 0 | aosReport.push_back(CPLSPrintf( |
434 | 0 | "Array %s: cannot compute array of differences", osName.c_str())); |
435 | 0 | return; |
436 | 0 | } |
437 | | |
438 | 0 | int nCountMetrics = 0; |
439 | 0 | if (HasMetric(METRIC_DIFF)) |
440 | 0 | ++nCountMetrics; |
441 | 0 | if (HasMetric(METRIC_RMSD) || HasMetric(METRIC_PSNR)) |
442 | 0 | ++nCountMetrics; |
443 | |
|
444 | 0 | double dfLastPct = 0; |
445 | 0 | if (HasMetric(METRIC_DIFF)) |
446 | 0 | { |
447 | 0 | double dfMin = 0, dfMax = 0; |
448 | |
|
449 | 0 | const double dfNewLastPct = 1.0 / nCountMetrics; |
450 | 0 | std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> |
451 | 0 | pScaledProgress(GDALCreateScaledProgress( |
452 | 0 | 0.0, dfNewLastPct, pfnProgress, pProgressData), |
453 | 0 | GDALDestroyScaledProgress); |
454 | 0 | dfLastPct = dfNewLastPct; |
455 | |
|
456 | 0 | if (!diffArray->ComputeStatistics( |
457 | 0 | /* bApproxOK =*/false, &dfMin, &dfMax, nullptr, nullptr, |
458 | 0 | nullptr, pScaledProgress ? GDALScaledProgress : nullptr, |
459 | 0 | pScaledProgress.get(), nullptr)) |
460 | 0 | { |
461 | 0 | aosReport.push_back(CPLSPrintf( |
462 | 0 | "Array %s: cannot compute statistics", osName.c_str())); |
463 | 0 | return; |
464 | 0 | } |
465 | 0 | if (dfMin != 0 || dfMax != 0) |
466 | 0 | { |
467 | 0 | const double dfMaxDiff = |
468 | 0 | std::max(std::fabs(dfMin), std::fabs(dfMax)); |
469 | 0 | aosReport.push_back( |
470 | 0 | CPLSPrintf("Array %s: maximum pixel value difference: %g", |
471 | 0 | osName.c_str(), dfMaxDiff)); |
472 | |
|
473 | 0 | NonZeroValueIterator it; |
474 | 0 | std::vector<GUInt64> arrayStartIdx(nDims, 0); |
475 | 0 | std::vector<GUInt64> count(nDims, 0); |
476 | 0 | for (size_t i = 0; i < nDims; ++i) |
477 | 0 | count[i] = diffArray->GetDimensions()[i]->GetSize(); |
478 | |
|
479 | 0 | size_t nMaxSize = 100 * 1024 * 1024; |
480 | 0 | const GIntBig nUsableRAM = CPLGetUsablePhysicalRAM() / 10; |
481 | 0 | if (nUsableRAM > 0) |
482 | 0 | nMaxSize = static_cast<size_t>(nUsableRAM); |
483 | |
|
484 | 0 | if (!diffArray->ProcessPerChunk( |
485 | 0 | arrayStartIdx.data(), count.data(), |
486 | 0 | diffArray->GetProcessingChunkSize(nMaxSize).data(), |
487 | 0 | NonZeroValueIterator::func, &it)) |
488 | 0 | { |
489 | 0 | aosReport.push_back( |
490 | 0 | CPLSPrintf("Array %s: diffArray->ProcessPerChunk() failed", |
491 | 0 | osName.c_str())); |
492 | 0 | return; |
493 | 0 | } |
494 | | |
495 | 0 | aosReport.push_back( |
496 | 0 | CPLSPrintf("Array %s: pixels differing: %" PRIu64, |
497 | 0 | osName.c_str(), it.nNonZero)); |
498 | 0 | } |
499 | 0 | } |
500 | | |
501 | 0 | if (HasMetric(METRIC_RMSD) || HasMetric(METRIC_PSNR)) |
502 | 0 | { |
503 | | // For PSNR on floating point image, we need to compute min and max of |
504 | | // reference band |
505 | 0 | const bool bIsInteger = CPL_TO_BOOL(GDALDataTypeIsInteger( |
506 | 0 | poRefArray->GetDataType().GetNumericDataType())); |
507 | 0 | const double dfScalingProgress = |
508 | 0 | HasMetric(METRIC_PSNR) && !bIsInteger ? 0.5 : 1; |
509 | 0 | const double dfNewLastPct = |
510 | 0 | std::min(1.0, dfLastPct + dfScalingProgress * (1.0 - dfLastPct)); |
511 | 0 | std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> |
512 | 0 | pScaledProgress(GDALCreateScaledProgress(dfLastPct, dfNewLastPct, |
513 | 0 | pfnProgress, |
514 | 0 | pProgressData), |
515 | 0 | GDALDestroyScaledProgress); |
516 | 0 | dfLastPct = dfNewLastPct; |
517 | |
|
518 | 0 | auto squaredDiffArray = (*diffArray) * diffArray; |
519 | 0 | double dfMeanSquareError = 0; |
520 | 0 | if (squaredDiffArray->ComputeStatistics( |
521 | 0 | /* bApproxOK = */ false, |
522 | 0 | /* pdfMin = */ nullptr, |
523 | 0 | /* pdfMax = */ nullptr, &dfMeanSquareError, |
524 | 0 | /* pdfStdDev = */ nullptr, nullptr, |
525 | 0 | pScaledProgress ? GDALScaledProgress : nullptr, |
526 | 0 | pScaledProgress.get(), nullptr)) |
527 | 0 | { |
528 | 0 | const double dfRMSD = std::sqrt(dfMeanSquareError); |
529 | 0 | if (dfRMSD > 0) |
530 | 0 | { |
531 | 0 | if (HasMetric(METRIC_RMSD)) |
532 | 0 | { |
533 | 0 | aosReport.push_back(CPLSPrintf("Array %s: RMSD: %g", |
534 | 0 | osName.c_str(), dfRMSD)); |
535 | 0 | } |
536 | |
|
537 | 0 | if (HasMetric(METRIC_PSNR)) |
538 | 0 | { |
539 | 0 | if (bIsInteger) |
540 | 0 | { |
541 | 0 | double dfMaxAmplitude = |
542 | 0 | std::pow(2.0, GDALGetDataTypeSizeBits( |
543 | 0 | poRefArray->GetDataType() |
544 | 0 | .GetNumericDataType())) - |
545 | 0 | 1; |
546 | |
|
547 | 0 | const double dfPSNR_dB = |
548 | 0 | 20 * std::log10(dfMaxAmplitude / dfRMSD); |
549 | 0 | aosReport.push_back( |
550 | 0 | CPLSPrintf("Array %s: PSNR (dB): %g", |
551 | 0 | osName.c_str(), dfPSNR_dB)); |
552 | 0 | } |
553 | 0 | else |
554 | 0 | { |
555 | 0 | double dfMin = 0; |
556 | 0 | double dfMax = 0; |
557 | 0 | const char *const apszOptions[] = { |
558 | 0 | "SET_STATISTICS=FALSE", nullptr}; |
559 | |
|
560 | 0 | pScaledProgress.reset(GDALCreateScaledProgress( |
561 | 0 | dfLastPct, 1.0, pfnProgress, pProgressData)); |
562 | |
|
563 | 0 | if (poRefArray->ComputeStatistics( |
564 | 0 | /* bApproxOK = */ false, &dfMin, &dfMax, |
565 | 0 | nullptr, nullptr, nullptr, |
566 | 0 | pScaledProgress ? GDALScaledProgress : nullptr, |
567 | 0 | pScaledProgress.get(), apszOptions)) |
568 | 0 | { |
569 | 0 | const double dfPSNR_dB = |
570 | 0 | 20 * std::log10((dfMax - dfMin) / dfRMSD); |
571 | 0 | aosReport.push_back( |
572 | 0 | CPLSPrintf("Array %s: PSNR (dB): %g", |
573 | 0 | osName.c_str(), dfPSNR_dB)); |
574 | 0 | } |
575 | 0 | else |
576 | 0 | { |
577 | 0 | aosReport.push_back( |
578 | 0 | std::string("Error during PSNR computation: ") |
579 | 0 | .append(CPLGetLastErrorMsg())); |
580 | 0 | } |
581 | 0 | } |
582 | 0 | } |
583 | 0 | } |
584 | 0 | } |
585 | 0 | else |
586 | 0 | { |
587 | 0 | aosReport.push_back( |
588 | 0 | std::string("Error during RMSD/PSNR computation: ") |
589 | 0 | .append(CPLGetLastErrorMsg())); |
590 | 0 | } |
591 | 0 | } |
592 | 0 | } |
593 | | |
594 | | /************************************************************************/ |
595 | | /* ~GDALMdimCompareAlgorithmStandalone() */ |
596 | | /************************************************************************/ |
597 | | |
598 | 0 | GDALMdimCompareAlgorithmStandalone::~GDALMdimCompareAlgorithmStandalone() = |
599 | | default; |
600 | | |
601 | | //! @endcond |