/src/gdal/frmts/vrt/vrtprocesseddatasetfunctions.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Virtual GDAL Datasets |
4 | | * Purpose: Implementation of VRTProcessedDataset processing functions |
5 | | * Author: Even Rouault <even.rouault at spatialys.com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2024, Even Rouault <even.rouault at spatialys.com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "cpl_float.h" |
14 | | #include "cpl_minixml.h" |
15 | | #include "cpl_string.h" |
16 | | #include "vrtdataset.h" |
17 | | #include "vrtexpression.h" |
18 | | |
19 | | #include <algorithm> |
20 | | #include <functional> |
21 | | #include <limits> |
22 | | #include <map> |
23 | | #include <optional> |
24 | | #include <set> |
25 | | #include <vector> |
26 | | |
27 | | /************************************************************************/ |
28 | | /* GetDstValue() */ |
29 | | /************************************************************************/ |
30 | | |
31 | | /** Return a destination value given an initial value, the destination no data |
32 | | * value and its replacement value |
33 | | */ |
34 | | static inline double GetDstValue(double dfVal, double dfDstNoData, |
35 | | double dfReplacementDstNodata, |
36 | | GDALDataType eIntendedDstDT, |
37 | | bool bDstIntendedDTIsInteger) |
38 | 0 | { |
39 | 0 | if (bDstIntendedDTIsInteger && std::round(dfVal) == dfDstNoData) |
40 | 0 | { |
41 | 0 | return dfReplacementDstNodata; |
42 | 0 | } |
43 | 0 | else if (eIntendedDstDT == GDT_Float16 && |
44 | 0 | static_cast<GFloat16>(dfVal) == static_cast<GFloat16>(dfDstNoData)) |
45 | 0 | { |
46 | 0 | return dfReplacementDstNodata; |
47 | 0 | } |
48 | 0 | else if (eIntendedDstDT == GDT_Float32 && |
49 | 0 | static_cast<float>(dfVal) == static_cast<float>(dfDstNoData)) |
50 | 0 | { |
51 | 0 | return dfReplacementDstNodata; |
52 | 0 | } |
53 | 0 | else if (eIntendedDstDT == GDT_Float64 && dfVal == dfDstNoData) |
54 | 0 | { |
55 | 0 | return dfReplacementDstNodata; |
56 | 0 | } |
57 | 0 | else |
58 | 0 | { |
59 | 0 | return dfVal; |
60 | 0 | } |
61 | 0 | } |
62 | | |
63 | | /************************************************************************/ |
64 | | /* BandAffineCombinationData */ |
65 | | /************************************************************************/ |
66 | | |
67 | | namespace |
68 | | { |
69 | | /** Working structure for 'BandAffineCombination' builtin function. */ |
70 | | struct BandAffineCombinationData |
71 | | { |
72 | | static constexpr const char *const EXPECTED_SIGNATURE = |
73 | | "BandAffineCombination"; |
74 | | //! Signature (to make sure callback functions are called with the right argument) |
75 | | const std::string m_osSignature = EXPECTED_SIGNATURE; |
76 | | |
77 | | /** Replacement nodata value */ |
78 | | std::vector<double> m_adfReplacementDstNodata{}; |
79 | | |
80 | | /** Intended destination data type. */ |
81 | | GDALDataType m_eIntendedDstDT = GDT_Float64; |
82 | | |
83 | | /** Affine transformation coefficients. |
84 | | * m_aadfCoefficients[i][0] is the constant term for the i(th) dst band |
85 | | * m_aadfCoefficients[i][j] is the weight of the j(th) src band for the |
86 | | * i(th) dst vand. |
87 | | * Said otherwise dst[i] = m_aadfCoefficients[i][0] + |
88 | | * sum(m_aadfCoefficients[i][j + 1] * src[j] for j in 0...nSrcBands-1) |
89 | | */ |
90 | | std::vector<std::vector<double>> m_aadfCoefficients{}; |
91 | | |
92 | | //! Minimum clamping value. |
93 | | double m_dfClampMin = std::numeric_limits<double>::quiet_NaN(); |
94 | | |
95 | | //! Maximum clamping value. |
96 | | double m_dfClampMax = std::numeric_limits<double>::quiet_NaN(); |
97 | | }; |
98 | | } // namespace |
99 | | |
100 | | /************************************************************************/ |
101 | | /* SetOutputValuesForInNoDataAndOutNoData() */ |
102 | | /************************************************************************/ |
103 | | |
104 | | static std::vector<double> SetOutputValuesForInNoDataAndOutNoData( |
105 | | int nInBands, double *padfInNoData, int *pnOutBands, |
106 | | double **ppadfOutNoData, bool bSrcNodataSpecified, double dfSrcNoData, |
107 | | bool bDstNodataSpecified, double dfDstNoData, bool bIsFinalStep) |
108 | 0 | { |
109 | 0 | if (bSrcNodataSpecified) |
110 | 0 | { |
111 | 0 | std::vector<double> adfNoData(nInBands, dfSrcNoData); |
112 | 0 | memcpy(padfInNoData, adfNoData.data(), |
113 | 0 | adfNoData.size() * sizeof(double)); |
114 | 0 | } |
115 | |
|
116 | 0 | std::vector<double> adfDstNoData; |
117 | 0 | if (bDstNodataSpecified) |
118 | 0 | { |
119 | 0 | adfDstNoData.resize(*pnOutBands, dfDstNoData); |
120 | 0 | } |
121 | 0 | else if (bIsFinalStep) |
122 | 0 | { |
123 | 0 | adfDstNoData = |
124 | 0 | std::vector<double>(*ppadfOutNoData, *ppadfOutNoData + *pnOutBands); |
125 | 0 | } |
126 | 0 | else |
127 | 0 | { |
128 | 0 | adfDstNoData = |
129 | 0 | std::vector<double>(padfInNoData, padfInNoData + nInBands); |
130 | 0 | adfDstNoData.resize(*pnOutBands, *padfInNoData); |
131 | 0 | } |
132 | |
|
133 | 0 | if (*ppadfOutNoData == nullptr) |
134 | 0 | { |
135 | 0 | *ppadfOutNoData = |
136 | 0 | static_cast<double *>(CPLMalloc(*pnOutBands * sizeof(double))); |
137 | 0 | } |
138 | 0 | memcpy(*ppadfOutNoData, adfDstNoData.data(), *pnOutBands * sizeof(double)); |
139 | |
|
140 | 0 | return adfDstNoData; |
141 | 0 | } |
142 | | |
143 | | /************************************************************************/ |
144 | | /* BandAffineCombinationInit() */ |
145 | | /************************************************************************/ |
146 | | |
147 | | /** Init function for 'BandAffineCombination' builtin function. */ |
148 | | static CPLErr BandAffineCombinationInit( |
149 | | const char * /*pszFuncName*/, void * /*pUserData*/, |
150 | | CSLConstList papszFunctionArgs, int nInBands, GDALDataType eInDT, |
151 | | double *padfInNoData, int *pnOutBands, GDALDataType *peOutDT, |
152 | | double **ppadfOutNoData, const char * /* pszVRTPath */, |
153 | | VRTPDWorkingDataPtr *ppWorkingData) |
154 | 0 | { |
155 | 0 | CPLAssert(eInDT == GDT_Float64); |
156 | | |
157 | 0 | *peOutDT = eInDT; |
158 | 0 | *ppWorkingData = nullptr; |
159 | |
|
160 | 0 | auto data = std::make_unique<BandAffineCombinationData>(); |
161 | |
|
162 | 0 | std::map<int, std::vector<double>> oMapCoefficients{}; |
163 | 0 | double dfSrcNoData = std::numeric_limits<double>::quiet_NaN(); |
164 | 0 | bool bSrcNodataSpecified = false; |
165 | 0 | double dfDstNoData = std::numeric_limits<double>::quiet_NaN(); |
166 | 0 | bool bDstNodataSpecified = false; |
167 | 0 | double dfReplacementDstNodata = std::numeric_limits<double>::quiet_NaN(); |
168 | 0 | bool bReplacementDstNodataSpecified = false; |
169 | |
|
170 | 0 | for (const auto &[pszKey, pszValue] : |
171 | 0 | cpl::IterateNameValue(papszFunctionArgs)) |
172 | 0 | { |
173 | 0 | if (EQUAL(pszKey, "src_nodata")) |
174 | 0 | { |
175 | 0 | bSrcNodataSpecified = true; |
176 | 0 | dfSrcNoData = CPLAtof(pszValue); |
177 | 0 | } |
178 | 0 | else if (EQUAL(pszKey, "dst_nodata")) |
179 | 0 | { |
180 | 0 | bDstNodataSpecified = true; |
181 | 0 | dfDstNoData = CPLAtof(pszValue); |
182 | 0 | } |
183 | 0 | else if (EQUAL(pszKey, "replacement_nodata")) |
184 | 0 | { |
185 | 0 | bReplacementDstNodataSpecified = true; |
186 | 0 | dfReplacementDstNodata = CPLAtof(pszValue); |
187 | 0 | } |
188 | 0 | else if (EQUAL(pszKey, "dst_intended_datatype")) |
189 | 0 | { |
190 | 0 | for (GDALDataType eDT = GDT_Byte; eDT < GDT_TypeCount; |
191 | 0 | eDT = static_cast<GDALDataType>(eDT + 1)) |
192 | 0 | { |
193 | 0 | if (EQUAL(GDALGetDataTypeName(eDT), pszValue)) |
194 | 0 | { |
195 | 0 | data->m_eIntendedDstDT = eDT; |
196 | 0 | break; |
197 | 0 | } |
198 | 0 | } |
199 | 0 | } |
200 | 0 | else if (STARTS_WITH_CI(pszKey, "coefficients_")) |
201 | 0 | { |
202 | 0 | const int nTargetBand = atoi(pszKey + strlen("coefficients_")); |
203 | 0 | if (nTargetBand <= 0 || nTargetBand > 65536) |
204 | 0 | { |
205 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
206 | 0 | "Invalid band in argument '%s'", pszKey); |
207 | 0 | return CE_Failure; |
208 | 0 | } |
209 | 0 | const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0)); |
210 | 0 | if (aosTokens.size() != 1 + nInBands) |
211 | 0 | { |
212 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
213 | 0 | "Argument %s has %d values, whereas %d are expected", |
214 | 0 | pszKey, aosTokens.size(), 1 + nInBands); |
215 | 0 | return CE_Failure; |
216 | 0 | } |
217 | 0 | std::vector<double> adfValues; |
218 | 0 | for (int i = 0; i < aosTokens.size(); ++i) |
219 | 0 | { |
220 | 0 | adfValues.push_back(CPLAtof(aosTokens[i])); |
221 | 0 | } |
222 | 0 | oMapCoefficients[nTargetBand - 1] = std::move(adfValues); |
223 | 0 | } |
224 | 0 | else if (EQUAL(pszKey, "min")) |
225 | 0 | { |
226 | 0 | data->m_dfClampMin = CPLAtof(pszValue); |
227 | 0 | } |
228 | 0 | else if (EQUAL(pszKey, "max")) |
229 | 0 | { |
230 | 0 | data->m_dfClampMax = CPLAtof(pszValue); |
231 | 0 | } |
232 | 0 | else |
233 | 0 | { |
234 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
235 | 0 | "Unrecognized argument name %s. Ignored", pszKey); |
236 | 0 | } |
237 | 0 | } |
238 | | |
239 | 0 | const bool bIsFinalStep = *pnOutBands != 0; |
240 | 0 | if (bIsFinalStep) |
241 | 0 | { |
242 | 0 | if (*pnOutBands != static_cast<int>(oMapCoefficients.size())) |
243 | 0 | { |
244 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
245 | 0 | "Final step expect %d bands, but only %d coefficient_XX " |
246 | 0 | "are provided", |
247 | 0 | *pnOutBands, static_cast<int>(oMapCoefficients.size())); |
248 | 0 | return CE_Failure; |
249 | 0 | } |
250 | 0 | } |
251 | 0 | else |
252 | 0 | { |
253 | 0 | *pnOutBands = static_cast<int>(oMapCoefficients.size()); |
254 | 0 | } |
255 | | |
256 | 0 | const std::vector<double> adfDstNoData = |
257 | 0 | SetOutputValuesForInNoDataAndOutNoData( |
258 | 0 | nInBands, padfInNoData, pnOutBands, ppadfOutNoData, |
259 | 0 | bSrcNodataSpecified, dfSrcNoData, bDstNodataSpecified, dfDstNoData, |
260 | 0 | bIsFinalStep); |
261 | |
|
262 | 0 | if (bReplacementDstNodataSpecified) |
263 | 0 | { |
264 | 0 | data->m_adfReplacementDstNodata.resize(*pnOutBands, |
265 | 0 | dfReplacementDstNodata); |
266 | 0 | } |
267 | 0 | else |
268 | 0 | { |
269 | 0 | for (double dfVal : adfDstNoData) |
270 | 0 | { |
271 | 0 | data->m_adfReplacementDstNodata.emplace_back( |
272 | 0 | GDALGetNoDataReplacementValue(data->m_eIntendedDstDT, dfVal)); |
273 | 0 | } |
274 | 0 | } |
275 | | |
276 | | // Check we have a set of coefficient for all output bands and |
277 | | // convert the map to a vector |
278 | 0 | for (auto &oIter : oMapCoefficients) |
279 | 0 | { |
280 | 0 | const int iExpected = static_cast<int>(data->m_aadfCoefficients.size()); |
281 | 0 | if (oIter.first != iExpected) |
282 | 0 | { |
283 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
284 | 0 | "Argument coefficients_%d is missing", iExpected + 1); |
285 | 0 | return CE_Failure; |
286 | 0 | } |
287 | 0 | data->m_aadfCoefficients.emplace_back(std::move(oIter.second)); |
288 | 0 | } |
289 | 0 | *ppWorkingData = data.release(); |
290 | 0 | return CE_None; |
291 | 0 | } |
292 | | |
293 | | /************************************************************************/ |
294 | | /* BandAffineCombinationFree() */ |
295 | | /************************************************************************/ |
296 | | |
297 | | /** Free function for 'BandAffineCombination' builtin function. */ |
298 | | static void BandAffineCombinationFree(const char * /*pszFuncName*/, |
299 | | void * /*pUserData*/, |
300 | | VRTPDWorkingDataPtr pWorkingData) |
301 | 0 | { |
302 | 0 | BandAffineCombinationData *data = |
303 | 0 | static_cast<BandAffineCombinationData *>(pWorkingData); |
304 | 0 | CPLAssert(data->m_osSignature == |
305 | 0 | BandAffineCombinationData::EXPECTED_SIGNATURE); |
306 | 0 | CPL_IGNORE_RET_VAL(data->m_osSignature); |
307 | 0 | delete data; |
308 | 0 | } |
309 | | |
310 | | /************************************************************************/ |
311 | | /* BandAffineCombinationProcess() */ |
312 | | /************************************************************************/ |
313 | | |
314 | | /** Processing function for 'BandAffineCombination' builtin function. */ |
315 | | static CPLErr BandAffineCombinationProcess( |
316 | | const char * /*pszFuncName*/, void * /*pUserData*/, |
317 | | VRTPDWorkingDataPtr pWorkingData, CSLConstList /* papszFunctionArgs*/, |
318 | | int nBufXSize, int nBufYSize, const void *pInBuffer, size_t nInBufferSize, |
319 | | GDALDataType eInDT, int nInBands, const double *CPL_RESTRICT padfInNoData, |
320 | | void *pOutBuffer, size_t nOutBufferSize, GDALDataType eOutDT, int nOutBands, |
321 | | const double *CPL_RESTRICT padfOutNoData, double /*dfSrcXOff*/, |
322 | | double /*dfSrcYOff*/, double /*dfSrcXSize*/, double /*dfSrcYSize*/, |
323 | | const double /*adfSrcGT*/[], const char * /* pszVRTPath */, |
324 | | CSLConstList /*papszExtra*/) |
325 | 0 | { |
326 | 0 | const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize; |
327 | |
|
328 | 0 | CPL_IGNORE_RET_VAL(eInDT); |
329 | 0 | CPLAssert(eInDT == GDT_Float64); |
330 | 0 | CPL_IGNORE_RET_VAL(eOutDT); |
331 | 0 | CPLAssert(eOutDT == GDT_Float64); |
332 | 0 | CPL_IGNORE_RET_VAL(nInBufferSize); |
333 | 0 | CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double)); |
334 | 0 | CPL_IGNORE_RET_VAL(nOutBufferSize); |
335 | 0 | CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double)); |
336 | | |
337 | 0 | const BandAffineCombinationData *data = |
338 | 0 | static_cast<BandAffineCombinationData *>(pWorkingData); |
339 | 0 | CPLAssert(data->m_osSignature == |
340 | 0 | BandAffineCombinationData::EXPECTED_SIGNATURE); |
341 | 0 | const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer); |
342 | 0 | double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer); |
343 | 0 | const bool bDstIntendedDTIsInteger = |
344 | 0 | GDALDataTypeIsInteger(data->m_eIntendedDstDT); |
345 | 0 | const double dfClampMin = data->m_dfClampMin; |
346 | 0 | const double dfClampMax = data->m_dfClampMax; |
347 | 0 | for (size_t i = 0; i < nElts; ++i) |
348 | 0 | { |
349 | 0 | for (int iDst = 0; iDst < nOutBands; ++iDst) |
350 | 0 | { |
351 | 0 | const auto &adfCoefficients = data->m_aadfCoefficients[iDst]; |
352 | 0 | double dfVal = adfCoefficients[0]; |
353 | 0 | bool bSetNoData = false; |
354 | 0 | for (int iSrc = 0; iSrc < nInBands; ++iSrc) |
355 | 0 | { |
356 | | // written this way to work with a NaN value |
357 | 0 | if (!(padfSrc[iSrc] != padfInNoData[iSrc])) |
358 | 0 | { |
359 | 0 | bSetNoData = true; |
360 | 0 | break; |
361 | 0 | } |
362 | 0 | dfVal += adfCoefficients[iSrc + 1] * padfSrc[iSrc]; |
363 | 0 | } |
364 | 0 | if (bSetNoData) |
365 | 0 | { |
366 | 0 | *padfDst = padfOutNoData[iDst]; |
367 | 0 | } |
368 | 0 | else |
369 | 0 | { |
370 | 0 | double dfDstVal = GetDstValue( |
371 | 0 | dfVal, padfOutNoData[iDst], |
372 | 0 | data->m_adfReplacementDstNodata[iDst], |
373 | 0 | data->m_eIntendedDstDT, bDstIntendedDTIsInteger); |
374 | 0 | if (dfDstVal < dfClampMin) |
375 | 0 | dfDstVal = dfClampMin; |
376 | 0 | if (dfDstVal > dfClampMax) |
377 | 0 | dfDstVal = dfClampMax; |
378 | 0 | *padfDst = dfDstVal; |
379 | 0 | } |
380 | 0 | ++padfDst; |
381 | 0 | } |
382 | 0 | padfSrc += nInBands; |
383 | 0 | } |
384 | |
|
385 | 0 | return CE_None; |
386 | 0 | } |
387 | | |
388 | | /************************************************************************/ |
389 | | /* LUTData */ |
390 | | /************************************************************************/ |
391 | | |
392 | | namespace |
393 | | { |
394 | | /** Working structure for 'LUT' builtin function. */ |
395 | | struct LUTData |
396 | | { |
397 | | static constexpr const char *const EXPECTED_SIGNATURE = "LUT"; |
398 | | //! Signature (to make sure callback functions are called with the right argument) |
399 | | const std::string m_osSignature = EXPECTED_SIGNATURE; |
400 | | |
401 | | //! m_aadfLUTInputs[i][j] is the j(th) input value for that LUT of band i. |
402 | | std::vector<std::vector<double>> m_aadfLUTInputs{}; |
403 | | |
404 | | //! m_aadfLUTOutputs[i][j] is the j(th) output value for that LUT of band i. |
405 | | std::vector<std::vector<double>> m_aadfLUTOutputs{}; |
406 | | |
407 | | /************************************************************************/ |
408 | | /* LookupValue() */ |
409 | | /************************************************************************/ |
410 | | |
411 | | double LookupValue(int iBand, double dfInput) const |
412 | 0 | { |
413 | 0 | const auto &adfInput = m_aadfLUTInputs[iBand]; |
414 | 0 | const auto &afdOutput = m_aadfLUTOutputs[iBand]; |
415 | | |
416 | | // Find the index of the first element in the LUT input array that |
417 | | // is not smaller than the input value. |
418 | 0 | int i = static_cast<int>( |
419 | 0 | std::lower_bound(adfInput.data(), adfInput.data() + adfInput.size(), |
420 | 0 | dfInput) - |
421 | 0 | adfInput.data()); |
422 | |
|
423 | 0 | if (i == 0) |
424 | 0 | return afdOutput[0]; |
425 | | |
426 | | // If the index is beyond the end of the LUT input array, the input |
427 | | // value is larger than all the values in the array. |
428 | 0 | if (i == static_cast<int>(adfInput.size())) |
429 | 0 | return afdOutput.back(); |
430 | | |
431 | 0 | if (adfInput[i] == dfInput) |
432 | 0 | return afdOutput[i]; |
433 | | |
434 | | // Otherwise, interpolate. |
435 | 0 | return afdOutput[i - 1] + (dfInput - adfInput[i - 1]) * |
436 | 0 | ((afdOutput[i] - afdOutput[i - 1]) / |
437 | 0 | (adfInput[i] - adfInput[i - 1])); |
438 | 0 | } |
439 | | }; |
440 | | } // namespace |
441 | | |
442 | | /************************************************************************/ |
443 | | /* LUTInit() */ |
444 | | /************************************************************************/ |
445 | | |
446 | | /** Init function for 'LUT' builtin function. */ |
447 | | static CPLErr LUTInit(const char * /*pszFuncName*/, void * /*pUserData*/, |
448 | | CSLConstList papszFunctionArgs, int nInBands, |
449 | | GDALDataType eInDT, double *padfInNoData, int *pnOutBands, |
450 | | GDALDataType *peOutDT, double **ppadfOutNoData, |
451 | | const char * /* pszVRTPath */, |
452 | | VRTPDWorkingDataPtr *ppWorkingData) |
453 | 0 | { |
454 | 0 | CPLAssert(eInDT == GDT_Float64); |
455 | | |
456 | 0 | const bool bIsFinalStep = *pnOutBands != 0; |
457 | 0 | *peOutDT = eInDT; |
458 | 0 | *ppWorkingData = nullptr; |
459 | |
|
460 | 0 | if (!bIsFinalStep) |
461 | 0 | { |
462 | 0 | *pnOutBands = nInBands; |
463 | 0 | } |
464 | |
|
465 | 0 | auto data = std::make_unique<LUTData>(); |
466 | |
|
467 | 0 | double dfSrcNoData = std::numeric_limits<double>::quiet_NaN(); |
468 | 0 | bool bSrcNodataSpecified = false; |
469 | 0 | double dfDstNoData = std::numeric_limits<double>::quiet_NaN(); |
470 | 0 | bool bDstNodataSpecified = false; |
471 | |
|
472 | 0 | std::map<int, std::pair<std::vector<double>, std::vector<double>>> oMap{}; |
473 | |
|
474 | 0 | for (const auto &[pszKey, pszValue] : |
475 | 0 | cpl::IterateNameValue(papszFunctionArgs)) |
476 | 0 | { |
477 | 0 | if (EQUAL(pszKey, "src_nodata")) |
478 | 0 | { |
479 | 0 | bSrcNodataSpecified = true; |
480 | 0 | dfSrcNoData = CPLAtof(pszValue); |
481 | 0 | } |
482 | 0 | else if (EQUAL(pszKey, "dst_nodata")) |
483 | 0 | { |
484 | 0 | bDstNodataSpecified = true; |
485 | 0 | dfDstNoData = CPLAtof(pszValue); |
486 | 0 | } |
487 | 0 | else if (STARTS_WITH_CI(pszKey, "lut_")) |
488 | 0 | { |
489 | 0 | const int nBand = atoi(pszKey + strlen("lut_")); |
490 | 0 | if (nBand <= 0 || nBand > nInBands) |
491 | 0 | { |
492 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
493 | 0 | "Invalid band in argument '%s'", pszKey); |
494 | 0 | return CE_Failure; |
495 | 0 | } |
496 | 0 | const CPLStringList aosTokens(CSLTokenizeString2(pszValue, ",", 0)); |
497 | 0 | std::vector<double> adfInputValues; |
498 | 0 | std::vector<double> adfOutputValues; |
499 | 0 | for (int i = 0; i < aosTokens.size(); ++i) |
500 | 0 | { |
501 | 0 | const CPLStringList aosTokens2( |
502 | 0 | CSLTokenizeString2(aosTokens[i], ":", 0)); |
503 | 0 | if (aosTokens2.size() != 2) |
504 | 0 | { |
505 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
506 | 0 | "Invalid value for argument '%s'", pszKey); |
507 | 0 | return CE_Failure; |
508 | 0 | } |
509 | 0 | adfInputValues.push_back(CPLAtof(aosTokens2[0])); |
510 | 0 | adfOutputValues.push_back(CPLAtof(aosTokens2[1])); |
511 | 0 | } |
512 | 0 | oMap[nBand - 1] = std::pair(std::move(adfInputValues), |
513 | 0 | std::move(adfOutputValues)); |
514 | 0 | } |
515 | 0 | else |
516 | 0 | { |
517 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
518 | 0 | "Unrecognized argument name %s. Ignored", pszKey); |
519 | 0 | } |
520 | 0 | } |
521 | | |
522 | 0 | SetOutputValuesForInNoDataAndOutNoData( |
523 | 0 | nInBands, padfInNoData, pnOutBands, ppadfOutNoData, bSrcNodataSpecified, |
524 | 0 | dfSrcNoData, bDstNodataSpecified, dfDstNoData, bIsFinalStep); |
525 | |
|
526 | 0 | int iExpected = 0; |
527 | | // Check we have values for all bands and convert to vector |
528 | 0 | for (auto &oIter : oMap) |
529 | 0 | { |
530 | 0 | if (oIter.first != iExpected) |
531 | 0 | { |
532 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Argument lut_%d is missing", |
533 | 0 | iExpected + 1); |
534 | 0 | return CE_Failure; |
535 | 0 | } |
536 | 0 | ++iExpected; |
537 | 0 | data->m_aadfLUTInputs.emplace_back(std::move(oIter.second.first)); |
538 | 0 | data->m_aadfLUTOutputs.emplace_back(std::move(oIter.second.second)); |
539 | 0 | } |
540 | | |
541 | 0 | if (static_cast<int>(oMap.size()) < *pnOutBands) |
542 | 0 | { |
543 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Missing lut_XX element(s)"); |
544 | 0 | return CE_Failure; |
545 | 0 | } |
546 | | |
547 | 0 | *ppWorkingData = data.release(); |
548 | 0 | return CE_None; |
549 | 0 | } |
550 | | |
551 | | /************************************************************************/ |
552 | | /* LUTFree() */ |
553 | | /************************************************************************/ |
554 | | |
555 | | /** Free function for 'LUT' builtin function. */ |
556 | | static void LUTFree(const char * /*pszFuncName*/, void * /*pUserData*/, |
557 | | VRTPDWorkingDataPtr pWorkingData) |
558 | 0 | { |
559 | 0 | LUTData *data = static_cast<LUTData *>(pWorkingData); |
560 | 0 | CPLAssert(data->m_osSignature == LUTData::EXPECTED_SIGNATURE); |
561 | 0 | CPL_IGNORE_RET_VAL(data->m_osSignature); |
562 | 0 | delete data; |
563 | 0 | } |
564 | | |
565 | | /************************************************************************/ |
566 | | /* LUTProcess() */ |
567 | | /************************************************************************/ |
568 | | |
569 | | /** Processing function for 'LUT' builtin function. */ |
570 | | static CPLErr |
571 | | LUTProcess(const char * /*pszFuncName*/, void * /*pUserData*/, |
572 | | VRTPDWorkingDataPtr pWorkingData, |
573 | | CSLConstList /* papszFunctionArgs*/, int nBufXSize, int nBufYSize, |
574 | | const void *pInBuffer, size_t nInBufferSize, GDALDataType eInDT, |
575 | | int nInBands, const double *CPL_RESTRICT padfInNoData, |
576 | | void *pOutBuffer, size_t nOutBufferSize, GDALDataType eOutDT, |
577 | | int nOutBands, const double *CPL_RESTRICT padfOutNoData, |
578 | | double /*dfSrcXOff*/, double /*dfSrcYOff*/, double /*dfSrcXSize*/, |
579 | | double /*dfSrcYSize*/, const double /*adfSrcGT*/[], |
580 | | const char * /* pszVRTPath */, CSLConstList /*papszExtra*/) |
581 | 0 | { |
582 | 0 | const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize; |
583 | |
|
584 | 0 | CPL_IGNORE_RET_VAL(eInDT); |
585 | 0 | CPLAssert(eInDT == GDT_Float64); |
586 | 0 | CPL_IGNORE_RET_VAL(eOutDT); |
587 | 0 | CPLAssert(eOutDT == GDT_Float64); |
588 | 0 | CPL_IGNORE_RET_VAL(nInBufferSize); |
589 | 0 | CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double)); |
590 | 0 | CPL_IGNORE_RET_VAL(nOutBufferSize); |
591 | 0 | CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double)); |
592 | 0 | CPLAssert(nInBands == nOutBands); |
593 | 0 | CPL_IGNORE_RET_VAL(nOutBands); |
594 | |
|
595 | 0 | const LUTData *data = static_cast<LUTData *>(pWorkingData); |
596 | 0 | CPLAssert(data->m_osSignature == LUTData::EXPECTED_SIGNATURE); |
597 | 0 | const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer); |
598 | 0 | double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer); |
599 | 0 | for (size_t i = 0; i < nElts; ++i) |
600 | 0 | { |
601 | 0 | for (int iBand = 0; iBand < nInBands; ++iBand) |
602 | 0 | { |
603 | | // written this way to work with a NaN value |
604 | 0 | if (!(*padfSrc != padfInNoData[iBand])) |
605 | 0 | *padfDst = padfOutNoData[iBand]; |
606 | 0 | else |
607 | 0 | *padfDst = data->LookupValue(iBand, *padfSrc); |
608 | 0 | ++padfSrc; |
609 | 0 | ++padfDst; |
610 | 0 | } |
611 | 0 | } |
612 | |
|
613 | 0 | return CE_None; |
614 | 0 | } |
615 | | |
616 | | /************************************************************************/ |
617 | | /* LocalScaleOffsetData */ |
618 | | /************************************************************************/ |
619 | | |
620 | | namespace |
621 | | { |
622 | | /** Working structure for 'LocalScaleOffset' builtin function. */ |
623 | | struct LocalScaleOffsetData |
624 | | { |
625 | | static constexpr const char *const EXPECTED_SIGNATURE = "LocalScaleOffset"; |
626 | | //! Signature (to make sure callback functions are called with the right argument) |
627 | | const std::string m_osSignature = EXPECTED_SIGNATURE; |
628 | | |
629 | | //! Nodata value for gain dataset(s) |
630 | | double m_dfGainNodata = std::numeric_limits<double>::quiet_NaN(); |
631 | | |
632 | | //! Nodata value for offset dataset(s) |
633 | | double m_dfOffsetNodata = std::numeric_limits<double>::quiet_NaN(); |
634 | | |
635 | | //! Minimum clamping value. |
636 | | double m_dfClampMin = std::numeric_limits<double>::quiet_NaN(); |
637 | | |
638 | | //! Maximum clamping value. |
639 | | double m_dfClampMax = std::numeric_limits<double>::quiet_NaN(); |
640 | | |
641 | | //! Map from gain/offset dataset name to datasets |
642 | | std::map<std::string, std::unique_ptr<GDALDataset>> m_oDatasetMap{}; |
643 | | |
644 | | //! Vector of size nInBands that point to the raster band from which to read gains. |
645 | | std::vector<GDALRasterBand *> m_oGainBands{}; |
646 | | |
647 | | //! Vector of size nInBands that point to the raster band from which to read offsets. |
648 | | std::vector<GDALRasterBand *> m_oOffsetBands{}; |
649 | | |
650 | | //! Working buffer that contain gain values. |
651 | | std::vector<VRTProcessedDataset::NoInitByte> m_abyGainBuffer{}; |
652 | | |
653 | | //! Working buffer that contain offset values. |
654 | | std::vector<VRTProcessedDataset::NoInitByte> m_abyOffsetBuffer{}; |
655 | | }; |
656 | | } // namespace |
657 | | |
658 | | /************************************************************************/ |
659 | | /* CheckAllBands() */ |
660 | | /************************************************************************/ |
661 | | |
662 | | /** Return true if the key of oMap is the sequence of all integers between |
663 | | * 0 and nExpectedBandCount-1. |
664 | | */ |
665 | | template <class T> |
666 | | static bool CheckAllBands(const std::map<int, T> &oMap, int nExpectedBandCount) |
667 | 0 | { |
668 | 0 | int iExpected = 0; |
669 | 0 | for (const auto &kv : oMap) |
670 | 0 | { |
671 | 0 | if (kv.first != iExpected) |
672 | 0 | return false; |
673 | 0 | ++iExpected; |
674 | 0 | } |
675 | 0 | return iExpected == nExpectedBandCount; |
676 | 0 | } Unexecuted instantiation: vrtprocesseddatasetfunctions.cpp:bool CheckAllBands<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >(std::__1::map<int, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::less<int>, std::__1::allocator<std::__1::pair<int const, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&, int) Unexecuted instantiation: vrtprocesseddatasetfunctions.cpp:bool CheckAllBands<int>(std::__1::map<int, int, std::__1::less<int>, std::__1::allocator<std::__1::pair<int const, int> > > const&, int) |
677 | | |
678 | | /************************************************************************/ |
679 | | /* LocalScaleOffsetInit() */ |
680 | | /************************************************************************/ |
681 | | |
682 | | /** Init function for 'LocalScaleOffset' builtin function. */ |
683 | | static CPLErr |
684 | | LocalScaleOffsetInit(const char * /*pszFuncName*/, void * /*pUserData*/, |
685 | | CSLConstList papszFunctionArgs, int nInBands, |
686 | | GDALDataType eInDT, double *padfInNoData, int *pnOutBands, |
687 | | GDALDataType *peOutDT, double **ppadfOutNoData, |
688 | | const char *pszVRTPath, VRTPDWorkingDataPtr *ppWorkingData) |
689 | 0 | { |
690 | 0 | CPLAssert(eInDT == GDT_Float64); |
691 | | |
692 | 0 | const bool bIsFinalStep = *pnOutBands != 0; |
693 | 0 | *peOutDT = eInDT; |
694 | 0 | *ppWorkingData = nullptr; |
695 | |
|
696 | 0 | if (!bIsFinalStep) |
697 | 0 | { |
698 | 0 | *pnOutBands = nInBands; |
699 | 0 | } |
700 | |
|
701 | 0 | auto data = std::make_unique<LocalScaleOffsetData>(); |
702 | |
|
703 | 0 | bool bNodataSpecified = false; |
704 | 0 | double dfNoData = std::numeric_limits<double>::quiet_NaN(); |
705 | |
|
706 | 0 | bool bGainNodataSpecified = false; |
707 | 0 | bool bOffsetNodataSpecified = false; |
708 | |
|
709 | 0 | std::map<int, std::string> oGainDatasetNameMap; |
710 | 0 | std::map<int, int> oGainDatasetBandMap; |
711 | |
|
712 | 0 | std::map<int, std::string> oOffsetDatasetNameMap; |
713 | 0 | std::map<int, int> oOffsetDatasetBandMap; |
714 | |
|
715 | 0 | bool bRelativeToVRT = false; |
716 | |
|
717 | 0 | for (const auto &[pszKey, pszValue] : |
718 | 0 | cpl::IterateNameValue(papszFunctionArgs)) |
719 | 0 | { |
720 | 0 | if (EQUAL(pszKey, "relativeToVRT")) |
721 | 0 | { |
722 | 0 | bRelativeToVRT = CPLTestBool(pszValue); |
723 | 0 | } |
724 | 0 | else if (EQUAL(pszKey, "nodata")) |
725 | 0 | { |
726 | 0 | bNodataSpecified = true; |
727 | 0 | dfNoData = CPLAtof(pszValue); |
728 | 0 | } |
729 | 0 | else if (EQUAL(pszKey, "gain_nodata")) |
730 | 0 | { |
731 | 0 | bGainNodataSpecified = true; |
732 | 0 | data->m_dfGainNodata = CPLAtof(pszValue); |
733 | 0 | } |
734 | 0 | else if (EQUAL(pszKey, "offset_nodata")) |
735 | 0 | { |
736 | 0 | bOffsetNodataSpecified = true; |
737 | 0 | data->m_dfOffsetNodata = CPLAtof(pszValue); |
738 | 0 | } |
739 | 0 | else if (STARTS_WITH_CI(pszKey, "gain_dataset_filename_")) |
740 | 0 | { |
741 | 0 | const int nBand = atoi(pszKey + strlen("gain_dataset_filename_")); |
742 | 0 | if (nBand <= 0 || nBand > nInBands) |
743 | 0 | { |
744 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
745 | 0 | "Invalid band in argument '%s'", pszKey); |
746 | 0 | return CE_Failure; |
747 | 0 | } |
748 | 0 | oGainDatasetNameMap[nBand - 1] = pszValue; |
749 | 0 | } |
750 | 0 | else if (STARTS_WITH_CI(pszKey, "gain_dataset_band_")) |
751 | 0 | { |
752 | 0 | const int nBand = atoi(pszKey + strlen("gain_dataset_band_")); |
753 | 0 | if (nBand <= 0 || nBand > nInBands) |
754 | 0 | { |
755 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
756 | 0 | "Invalid band in argument '%s'", pszKey); |
757 | 0 | return CE_Failure; |
758 | 0 | } |
759 | 0 | oGainDatasetBandMap[nBand - 1] = atoi(pszValue); |
760 | 0 | } |
761 | 0 | else if (STARTS_WITH_CI(pszKey, "offset_dataset_filename_")) |
762 | 0 | { |
763 | 0 | const int nBand = atoi(pszKey + strlen("offset_dataset_filename_")); |
764 | 0 | if (nBand <= 0 || nBand > nInBands) |
765 | 0 | { |
766 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
767 | 0 | "Invalid band in argument '%s'", pszKey); |
768 | 0 | return CE_Failure; |
769 | 0 | } |
770 | 0 | oOffsetDatasetNameMap[nBand - 1] = pszValue; |
771 | 0 | } |
772 | 0 | else if (STARTS_WITH_CI(pszKey, "offset_dataset_band_")) |
773 | 0 | { |
774 | 0 | const int nBand = atoi(pszKey + strlen("offset_dataset_band_")); |
775 | 0 | if (nBand <= 0 || nBand > nInBands) |
776 | 0 | { |
777 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
778 | 0 | "Invalid band in argument '%s'", pszKey); |
779 | 0 | return CE_Failure; |
780 | 0 | } |
781 | 0 | oOffsetDatasetBandMap[nBand - 1] = atoi(pszValue); |
782 | 0 | } |
783 | 0 | else if (EQUAL(pszKey, "min")) |
784 | 0 | { |
785 | 0 | data->m_dfClampMin = CPLAtof(pszValue); |
786 | 0 | } |
787 | 0 | else if (EQUAL(pszKey, "max")) |
788 | 0 | { |
789 | 0 | data->m_dfClampMax = CPLAtof(pszValue); |
790 | 0 | } |
791 | 0 | else |
792 | 0 | { |
793 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
794 | 0 | "Unrecognized argument name %s. Ignored", pszKey); |
795 | 0 | } |
796 | 0 | } |
797 | | |
798 | 0 | if (!CheckAllBands(oGainDatasetNameMap, nInBands)) |
799 | 0 | { |
800 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
801 | 0 | "Missing gain_dataset_filename_XX element(s)"); |
802 | 0 | return CE_Failure; |
803 | 0 | } |
804 | 0 | if (!CheckAllBands(oGainDatasetBandMap, nInBands)) |
805 | 0 | { |
806 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
807 | 0 | "Missing gain_dataset_band_XX element(s)"); |
808 | 0 | return CE_Failure; |
809 | 0 | } |
810 | 0 | if (!CheckAllBands(oOffsetDatasetNameMap, nInBands)) |
811 | 0 | { |
812 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
813 | 0 | "Missing offset_dataset_filename_XX element(s)"); |
814 | 0 | return CE_Failure; |
815 | 0 | } |
816 | 0 | if (!CheckAllBands(oOffsetDatasetBandMap, nInBands)) |
817 | 0 | { |
818 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
819 | 0 | "Missing offset_dataset_band_XX element(s)"); |
820 | 0 | return CE_Failure; |
821 | 0 | } |
822 | | |
823 | 0 | data->m_oGainBands.resize(nInBands); |
824 | 0 | data->m_oOffsetBands.resize(nInBands); |
825 | |
|
826 | 0 | constexpr int IDX_GAIN = 0; |
827 | 0 | constexpr int IDX_OFFSET = 1; |
828 | 0 | for (int i : {IDX_GAIN, IDX_OFFSET}) |
829 | 0 | { |
830 | 0 | const auto &oMapNames = |
831 | 0 | (i == IDX_GAIN) ? oGainDatasetNameMap : oOffsetDatasetNameMap; |
832 | 0 | const auto &oMapBands = |
833 | 0 | (i == IDX_GAIN) ? oGainDatasetBandMap : oOffsetDatasetBandMap; |
834 | 0 | for (const auto &kv : oMapNames) |
835 | 0 | { |
836 | 0 | const int nInBandIdx = kv.first; |
837 | 0 | const auto osFilename = GDALDataset::BuildFilename( |
838 | 0 | kv.second.c_str(), pszVRTPath, bRelativeToVRT); |
839 | 0 | auto oIter = data->m_oDatasetMap.find(osFilename); |
840 | 0 | if (oIter == data->m_oDatasetMap.end()) |
841 | 0 | { |
842 | 0 | auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open( |
843 | 0 | osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, |
844 | 0 | nullptr, nullptr, nullptr)); |
845 | 0 | if (!poDS) |
846 | 0 | return CE_Failure; |
847 | 0 | double adfAuxGT[6]; |
848 | 0 | if (poDS->GetGeoTransform(adfAuxGT) != CE_None) |
849 | 0 | { |
850 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
851 | 0 | "%s lacks a geotransform", osFilename.c_str()); |
852 | 0 | return CE_Failure; |
853 | 0 | } |
854 | 0 | oIter = data->m_oDatasetMap |
855 | 0 | .insert(std::pair(osFilename, std::move(poDS))) |
856 | 0 | .first; |
857 | 0 | } |
858 | 0 | auto poDS = oIter->second.get(); |
859 | 0 | const auto oIterBand = oMapBands.find(nInBandIdx); |
860 | 0 | CPLAssert(oIterBand != oMapBands.end()); |
861 | 0 | const int nAuxBand = oIterBand->second; |
862 | 0 | if (nAuxBand <= 0 || nAuxBand > poDS->GetRasterCount()) |
863 | 0 | { |
864 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
865 | 0 | "Invalid band number (%d) for a %s dataset", nAuxBand, |
866 | 0 | (i == IDX_GAIN) ? "gain" : "offset"); |
867 | 0 | return CE_Failure; |
868 | 0 | } |
869 | 0 | auto poAuxBand = poDS->GetRasterBand(nAuxBand); |
870 | 0 | int bAuxBandHasNoData = false; |
871 | 0 | const double dfAuxNoData = |
872 | 0 | poAuxBand->GetNoDataValue(&bAuxBandHasNoData); |
873 | 0 | if (i == IDX_GAIN) |
874 | 0 | { |
875 | 0 | data->m_oGainBands[nInBandIdx] = poAuxBand; |
876 | 0 | if (!bGainNodataSpecified && bAuxBandHasNoData) |
877 | 0 | data->m_dfGainNodata = dfAuxNoData; |
878 | 0 | } |
879 | 0 | else |
880 | 0 | { |
881 | 0 | data->m_oOffsetBands[nInBandIdx] = poAuxBand; |
882 | 0 | if (!bOffsetNodataSpecified && bAuxBandHasNoData) |
883 | 0 | data->m_dfOffsetNodata = dfAuxNoData; |
884 | 0 | } |
885 | 0 | } |
886 | 0 | } |
887 | | |
888 | 0 | SetOutputValuesForInNoDataAndOutNoData( |
889 | 0 | nInBands, padfInNoData, pnOutBands, ppadfOutNoData, bNodataSpecified, |
890 | 0 | dfNoData, bNodataSpecified, dfNoData, bIsFinalStep); |
891 | |
|
892 | 0 | *ppWorkingData = data.release(); |
893 | 0 | return CE_None; |
894 | 0 | } |
895 | | |
896 | | /************************************************************************/ |
897 | | /* LocalScaleOffsetFree() */ |
898 | | /************************************************************************/ |
899 | | |
900 | | /** Free function for 'LocalScaleOffset' builtin function. */ |
901 | | static void LocalScaleOffsetFree(const char * /*pszFuncName*/, |
902 | | void * /*pUserData*/, |
903 | | VRTPDWorkingDataPtr pWorkingData) |
904 | 0 | { |
905 | 0 | LocalScaleOffsetData *data = |
906 | 0 | static_cast<LocalScaleOffsetData *>(pWorkingData); |
907 | 0 | CPLAssert(data->m_osSignature == LocalScaleOffsetData::EXPECTED_SIGNATURE); |
908 | 0 | CPL_IGNORE_RET_VAL(data->m_osSignature); |
909 | 0 | delete data; |
910 | 0 | } |
911 | | |
912 | | /************************************************************************/ |
913 | | /* LoadAuxData() */ |
914 | | /************************************************************************/ |
915 | | |
916 | | // Load auxiliary corresponding offset, gain or trimming data. |
917 | | static bool LoadAuxData(double dfULX, double dfULY, double dfLRX, double dfLRY, |
918 | | size_t nElts, int nBufXSize, int nBufYSize, |
919 | | const char *pszAuxType, GDALRasterBand *poAuxBand, |
920 | | std::vector<VRTProcessedDataset::NoInitByte> &abyBuffer) |
921 | 0 | { |
922 | 0 | double adfAuxGT[6]; |
923 | 0 | double adfAuxInvGT[6]; |
924 | | |
925 | | // Compute pixel/line coordinates from the georeferenced extent |
926 | 0 | CPL_IGNORE_RET_VAL(poAuxBand->GetDataset()->GetGeoTransform( |
927 | 0 | adfAuxGT)); // return code already tested |
928 | 0 | CPL_IGNORE_RET_VAL(GDALInvGeoTransform(adfAuxGT, adfAuxInvGT)); |
929 | 0 | const double dfULPixel = |
930 | 0 | adfAuxInvGT[0] + adfAuxInvGT[1] * dfULX + adfAuxInvGT[2] * dfULY; |
931 | 0 | const double dfULLine = |
932 | 0 | adfAuxInvGT[3] + adfAuxInvGT[4] * dfULX + adfAuxInvGT[5] * dfULY; |
933 | 0 | const double dfLRPixel = |
934 | 0 | adfAuxInvGT[0] + adfAuxInvGT[1] * dfLRX + adfAuxInvGT[2] * dfLRY; |
935 | 0 | const double dfLRLine = |
936 | 0 | adfAuxInvGT[3] + adfAuxInvGT[4] * dfLRX + adfAuxInvGT[5] * dfLRY; |
937 | 0 | if (dfULPixel >= dfLRPixel || dfULLine >= dfLRLine) |
938 | 0 | { |
939 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
940 | 0 | "Unexpected computed %s pixel/line", pszAuxType); |
941 | 0 | return false; |
942 | 0 | } |
943 | 0 | if (dfULPixel < -1 || dfULLine < -1) |
944 | 0 | { |
945 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
946 | 0 | "Unexpected computed %s upper left (pixel,line)=(%f,%f)", |
947 | 0 | pszAuxType, dfULPixel, dfULLine); |
948 | 0 | return false; |
949 | 0 | } |
950 | 0 | if (dfLRPixel > poAuxBand->GetXSize() + 1 || |
951 | 0 | dfLRLine > poAuxBand->GetYSize() + 1) |
952 | 0 | { |
953 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
954 | 0 | "Unexpected computed %s lower right (pixel,line)=(%f,%f)", |
955 | 0 | pszAuxType, dfLRPixel, dfLRLine); |
956 | 0 | return false; |
957 | 0 | } |
958 | | |
959 | 0 | const int nAuxXOff = std::clamp(static_cast<int>(std::round(dfULPixel)), 0, |
960 | 0 | poAuxBand->GetXSize() - 1); |
961 | 0 | const int nAuxYOff = std::clamp(static_cast<int>(std::round(dfULLine)), 0, |
962 | 0 | poAuxBand->GetYSize() - 1); |
963 | 0 | const int nAuxX2Off = std::min(poAuxBand->GetXSize(), |
964 | 0 | static_cast<int>(std::round(dfLRPixel))); |
965 | 0 | const int nAuxY2Off = |
966 | 0 | std::min(poAuxBand->GetYSize(), static_cast<int>(std::round(dfLRLine))); |
967 | |
|
968 | 0 | try |
969 | 0 | { |
970 | 0 | abyBuffer.resize(nElts * sizeof(float)); |
971 | 0 | } |
972 | 0 | catch (const std::bad_alloc &) |
973 | 0 | { |
974 | 0 | CPLError(CE_Failure, CPLE_OutOfMemory, |
975 | 0 | "Out of memory allocating working buffer"); |
976 | 0 | return false; |
977 | 0 | } |
978 | 0 | GDALRasterIOExtraArg sExtraArg; |
979 | 0 | INIT_RASTERIO_EXTRA_ARG(sExtraArg); |
980 | 0 | sExtraArg.bFloatingPointWindowValidity = true; |
981 | 0 | CPL_IGNORE_RET_VAL(sExtraArg.eResampleAlg); |
982 | 0 | sExtraArg.eResampleAlg = GRIORA_Bilinear; |
983 | 0 | sExtraArg.dfXOff = std::max(0.0, dfULPixel); |
984 | 0 | sExtraArg.dfYOff = std::max(0.0, dfULLine); |
985 | 0 | sExtraArg.dfXSize = std::min<double>(poAuxBand->GetXSize(), dfLRPixel) - |
986 | 0 | std::max(0.0, dfULPixel); |
987 | 0 | sExtraArg.dfYSize = std::min<double>(poAuxBand->GetYSize(), dfLRLine) - |
988 | 0 | std::max(0.0, dfULLine); |
989 | 0 | return (poAuxBand->RasterIO( |
990 | 0 | GF_Read, nAuxXOff, nAuxYOff, std::max(1, nAuxX2Off - nAuxXOff), |
991 | 0 | std::max(1, nAuxY2Off - nAuxYOff), abyBuffer.data(), nBufXSize, |
992 | 0 | nBufYSize, GDT_Float32, 0, 0, &sExtraArg) == CE_None); |
993 | 0 | } |
994 | | |
995 | | /************************************************************************/ |
996 | | /* LocalScaleOffsetProcess() */ |
997 | | /************************************************************************/ |
998 | | |
999 | | /** Processing function for 'LocalScaleOffset' builtin function. */ |
1000 | | static CPLErr LocalScaleOffsetProcess( |
1001 | | const char * /*pszFuncName*/, void * /*pUserData*/, |
1002 | | VRTPDWorkingDataPtr pWorkingData, CSLConstList /* papszFunctionArgs*/, |
1003 | | int nBufXSize, int nBufYSize, const void *pInBuffer, size_t nInBufferSize, |
1004 | | GDALDataType eInDT, int nInBands, const double *CPL_RESTRICT padfInNoData, |
1005 | | void *pOutBuffer, size_t nOutBufferSize, GDALDataType eOutDT, int nOutBands, |
1006 | | const double *CPL_RESTRICT padfOutNoData, double dfSrcXOff, |
1007 | | double dfSrcYOff, double dfSrcXSize, double dfSrcYSize, |
1008 | | const double adfSrcGT[], const char * /* pszVRTPath */, |
1009 | | CSLConstList /*papszExtra*/) |
1010 | 0 | { |
1011 | 0 | const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize; |
1012 | |
|
1013 | 0 | CPL_IGNORE_RET_VAL(eInDT); |
1014 | 0 | CPLAssert(eInDT == GDT_Float64); |
1015 | 0 | CPL_IGNORE_RET_VAL(eOutDT); |
1016 | 0 | CPLAssert(eOutDT == GDT_Float64); |
1017 | 0 | CPL_IGNORE_RET_VAL(nInBufferSize); |
1018 | 0 | CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double)); |
1019 | 0 | CPL_IGNORE_RET_VAL(nOutBufferSize); |
1020 | 0 | CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double)); |
1021 | 0 | CPLAssert(nInBands == nOutBands); |
1022 | 0 | CPL_IGNORE_RET_VAL(nOutBands); |
1023 | |
|
1024 | 0 | LocalScaleOffsetData *data = |
1025 | 0 | static_cast<LocalScaleOffsetData *>(pWorkingData); |
1026 | 0 | CPLAssert(data->m_osSignature == LocalScaleOffsetData::EXPECTED_SIGNATURE); |
1027 | 0 | const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer); |
1028 | 0 | double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer); |
1029 | | |
1030 | | // Compute georeferenced extent of input region |
1031 | 0 | const double dfULX = |
1032 | 0 | adfSrcGT[0] + adfSrcGT[1] * dfSrcXOff + adfSrcGT[2] * dfSrcYOff; |
1033 | 0 | const double dfULY = |
1034 | 0 | adfSrcGT[3] + adfSrcGT[4] * dfSrcXOff + adfSrcGT[5] * dfSrcYOff; |
1035 | 0 | const double dfLRX = adfSrcGT[0] + adfSrcGT[1] * (dfSrcXOff + dfSrcXSize) + |
1036 | 0 | adfSrcGT[2] * (dfSrcYOff + dfSrcYSize); |
1037 | 0 | const double dfLRY = adfSrcGT[3] + adfSrcGT[4] * (dfSrcXOff + dfSrcXSize) + |
1038 | 0 | adfSrcGT[5] * (dfSrcYOff + dfSrcYSize); |
1039 | |
|
1040 | 0 | auto &abyOffsetBuffer = data->m_abyGainBuffer; |
1041 | 0 | auto &abyGainBuffer = data->m_abyOffsetBuffer; |
1042 | |
|
1043 | 0 | for (int iBand = 0; iBand < nInBands; ++iBand) |
1044 | 0 | { |
1045 | 0 | if (!LoadAuxData(dfULX, dfULY, dfLRX, dfLRY, nElts, nBufXSize, |
1046 | 0 | nBufYSize, "gain", data->m_oGainBands[iBand], |
1047 | 0 | abyGainBuffer) || |
1048 | 0 | !LoadAuxData(dfULX, dfULY, dfLRX, dfLRY, nElts, nBufXSize, |
1049 | 0 | nBufYSize, "offset", data->m_oOffsetBands[iBand], |
1050 | 0 | abyOffsetBuffer)) |
1051 | 0 | { |
1052 | 0 | return CE_Failure; |
1053 | 0 | } |
1054 | | |
1055 | 0 | const double *CPL_RESTRICT padfSrcThisBand = padfSrc + iBand; |
1056 | 0 | double *CPL_RESTRICT padfDstThisBand = padfDst + iBand; |
1057 | 0 | const float *pafGain = |
1058 | 0 | reinterpret_cast<const float *>(abyGainBuffer.data()); |
1059 | 0 | const float *pafOffset = |
1060 | 0 | reinterpret_cast<const float *>(abyOffsetBuffer.data()); |
1061 | 0 | const double dfSrcNodata = padfInNoData[iBand]; |
1062 | 0 | const double dfDstNodata = padfOutNoData[iBand]; |
1063 | 0 | const double dfGainNodata = data->m_dfGainNodata; |
1064 | 0 | const double dfOffsetNodata = data->m_dfOffsetNodata; |
1065 | 0 | const double dfClampMin = data->m_dfClampMin; |
1066 | 0 | const double dfClampMax = data->m_dfClampMax; |
1067 | 0 | for (size_t i = 0; i < nElts; ++i) |
1068 | 0 | { |
1069 | 0 | const double dfSrcVal = *padfSrcThisBand; |
1070 | | // written this way to work with a NaN value |
1071 | 0 | if (!(dfSrcVal != dfSrcNodata)) |
1072 | 0 | { |
1073 | 0 | *padfDstThisBand = dfDstNodata; |
1074 | 0 | } |
1075 | 0 | else |
1076 | 0 | { |
1077 | 0 | const double dfGain = pafGain[i]; |
1078 | 0 | const double dfOffset = pafOffset[i]; |
1079 | 0 | if (!(dfGain != dfGainNodata) || !(dfOffset != dfOffsetNodata)) |
1080 | 0 | { |
1081 | 0 | *padfDstThisBand = dfDstNodata; |
1082 | 0 | } |
1083 | 0 | else |
1084 | 0 | { |
1085 | 0 | double dfUnscaled = dfSrcVal * dfGain - dfOffset; |
1086 | 0 | if (dfUnscaled < dfClampMin) |
1087 | 0 | dfUnscaled = dfClampMin; |
1088 | 0 | if (dfUnscaled > dfClampMax) |
1089 | 0 | dfUnscaled = dfClampMax; |
1090 | |
|
1091 | 0 | *padfDstThisBand = dfUnscaled; |
1092 | 0 | } |
1093 | 0 | } |
1094 | 0 | padfSrcThisBand += nInBands; |
1095 | 0 | padfDstThisBand += nInBands; |
1096 | 0 | } |
1097 | 0 | } |
1098 | | |
1099 | 0 | return CE_None; |
1100 | 0 | } |
1101 | | |
1102 | | /************************************************************************/ |
1103 | | /* TrimmingData */ |
1104 | | /************************************************************************/ |
1105 | | |
1106 | | namespace |
1107 | | { |
1108 | | /** Working structure for 'Trimming' builtin function. */ |
1109 | | struct TrimmingData |
1110 | | { |
1111 | | static constexpr const char *const EXPECTED_SIGNATURE = "Trimming"; |
1112 | | //! Signature (to make sure callback functions are called with the right argument) |
1113 | | const std::string m_osSignature = EXPECTED_SIGNATURE; |
1114 | | |
1115 | | //! Nodata value for trimming dataset |
1116 | | double m_dfTrimmingNodata = std::numeric_limits<double>::quiet_NaN(); |
1117 | | |
1118 | | //! Maximum saturating RGB output value. |
1119 | | double m_dfTopRGB = 0; |
1120 | | |
1121 | | //! Maximum threshold beyond which we give up saturation |
1122 | | double m_dfToneCeil = 0; |
1123 | | |
1124 | | //! Margin to allow for dynamics in brighest areas (in [0,1] range) |
1125 | | double m_dfTopMargin = 0; |
1126 | | |
1127 | | //! Index (zero-based) of input/output red band. |
1128 | | int m_nRedBand = 1 - 1; |
1129 | | |
1130 | | //! Index (zero-based) of input/output green band. |
1131 | | int m_nGreenBand = 2 - 1; |
1132 | | |
1133 | | //! Index (zero-based) of input/output blue band. |
1134 | | int m_nBlueBand = 3 - 1; |
1135 | | |
1136 | | //! Trimming dataset |
1137 | | std::unique_ptr<GDALDataset> m_poTrimmingDS{}; |
1138 | | |
1139 | | //! Trimming raster band. |
1140 | | GDALRasterBand *m_poTrimmingBand = nullptr; |
1141 | | |
1142 | | //! Working buffer that contain trimming values. |
1143 | | std::vector<VRTProcessedDataset::NoInitByte> m_abyTrimmingBuffer{}; |
1144 | | }; |
1145 | | } // namespace |
1146 | | |
1147 | | /************************************************************************/ |
1148 | | /* TrimmingInit() */ |
1149 | | /************************************************************************/ |
1150 | | |
1151 | | /** Init function for 'Trimming' builtin function. */ |
1152 | | static CPLErr TrimmingInit(const char * /*pszFuncName*/, void * /*pUserData*/, |
1153 | | CSLConstList papszFunctionArgs, int nInBands, |
1154 | | GDALDataType eInDT, double *padfInNoData, |
1155 | | int *pnOutBands, GDALDataType *peOutDT, |
1156 | | double **ppadfOutNoData, const char *pszVRTPath, |
1157 | | VRTPDWorkingDataPtr *ppWorkingData) |
1158 | 0 | { |
1159 | 0 | CPLAssert(eInDT == GDT_Float64); |
1160 | | |
1161 | 0 | const bool bIsFinalStep = *pnOutBands != 0; |
1162 | 0 | *peOutDT = eInDT; |
1163 | 0 | *ppWorkingData = nullptr; |
1164 | |
|
1165 | 0 | if (!bIsFinalStep) |
1166 | 0 | { |
1167 | 0 | *pnOutBands = nInBands; |
1168 | 0 | } |
1169 | |
|
1170 | 0 | auto data = std::make_unique<TrimmingData>(); |
1171 | |
|
1172 | 0 | bool bNodataSpecified = false; |
1173 | 0 | double dfNoData = std::numeric_limits<double>::quiet_NaN(); |
1174 | 0 | std::string osTrimmingFilename; |
1175 | 0 | bool bTrimmingNodataSpecified = false; |
1176 | 0 | bool bRelativeToVRT = false; |
1177 | |
|
1178 | 0 | for (const auto &[pszKey, pszValue] : |
1179 | 0 | cpl::IterateNameValue(papszFunctionArgs)) |
1180 | 0 | { |
1181 | 0 | if (EQUAL(pszKey, "relativeToVRT")) |
1182 | 0 | { |
1183 | 0 | bRelativeToVRT = CPLTestBool(pszValue); |
1184 | 0 | } |
1185 | 0 | else if (EQUAL(pszKey, "nodata")) |
1186 | 0 | { |
1187 | 0 | bNodataSpecified = true; |
1188 | 0 | dfNoData = CPLAtof(pszValue); |
1189 | 0 | } |
1190 | 0 | else if (EQUAL(pszKey, "trimming_nodata")) |
1191 | 0 | { |
1192 | 0 | bTrimmingNodataSpecified = true; |
1193 | 0 | data->m_dfTrimmingNodata = CPLAtof(pszValue); |
1194 | 0 | } |
1195 | 0 | else if (EQUAL(pszKey, "trimming_dataset_filename")) |
1196 | 0 | { |
1197 | 0 | osTrimmingFilename = pszValue; |
1198 | 0 | } |
1199 | 0 | else if (EQUAL(pszKey, "red_band")) |
1200 | 0 | { |
1201 | 0 | const int nBand = atoi(pszValue) - 1; |
1202 | 0 | if (nBand < 0 || nBand >= nInBands) |
1203 | 0 | { |
1204 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1205 | 0 | "Invalid band in argument '%s'", pszKey); |
1206 | 0 | return CE_Failure; |
1207 | 0 | } |
1208 | 0 | data->m_nRedBand = nBand; |
1209 | 0 | } |
1210 | 0 | else if (EQUAL(pszKey, "green_band")) |
1211 | 0 | { |
1212 | 0 | const int nBand = atoi(pszValue) - 1; |
1213 | 0 | if (nBand < 0 || nBand >= nInBands) |
1214 | 0 | { |
1215 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1216 | 0 | "Invalid band in argument '%s'", pszKey); |
1217 | 0 | return CE_Failure; |
1218 | 0 | } |
1219 | 0 | data->m_nGreenBand = nBand; |
1220 | 0 | } |
1221 | 0 | else if (EQUAL(pszKey, "blue_band")) |
1222 | 0 | { |
1223 | 0 | const int nBand = atoi(pszValue) - 1; |
1224 | 0 | if (nBand < 0 || nBand >= nInBands) |
1225 | 0 | { |
1226 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1227 | 0 | "Invalid band in argument '%s'", pszKey); |
1228 | 0 | return CE_Failure; |
1229 | 0 | } |
1230 | 0 | data->m_nBlueBand = nBand; |
1231 | 0 | } |
1232 | 0 | else if (EQUAL(pszKey, "top_rgb")) |
1233 | 0 | { |
1234 | 0 | data->m_dfTopRGB = CPLAtof(pszValue); |
1235 | 0 | } |
1236 | 0 | else if (EQUAL(pszKey, "tone_ceil")) |
1237 | 0 | { |
1238 | 0 | data->m_dfToneCeil = CPLAtof(pszValue); |
1239 | 0 | } |
1240 | 0 | else if (EQUAL(pszKey, "top_margin")) |
1241 | 0 | { |
1242 | 0 | data->m_dfTopMargin = CPLAtof(pszValue); |
1243 | 0 | } |
1244 | 0 | else |
1245 | 0 | { |
1246 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1247 | 0 | "Unrecognized argument name %s. Ignored", pszKey); |
1248 | 0 | } |
1249 | 0 | } |
1250 | | |
1251 | 0 | if (data->m_nRedBand == data->m_nGreenBand || |
1252 | 0 | data->m_nRedBand == data->m_nBlueBand || |
1253 | 0 | data->m_nGreenBand == data->m_nBlueBand) |
1254 | 0 | { |
1255 | 0 | CPLError( |
1256 | 0 | CE_Failure, CPLE_NotSupported, |
1257 | 0 | "red_band, green_band and blue_band must have distinct values"); |
1258 | 0 | return CE_Failure; |
1259 | 0 | } |
1260 | | |
1261 | 0 | const auto osFilename = GDALDataset::BuildFilename( |
1262 | 0 | osTrimmingFilename.c_str(), pszVRTPath, bRelativeToVRT); |
1263 | 0 | data->m_poTrimmingDS.reset(GDALDataset::Open( |
1264 | 0 | osFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, nullptr, |
1265 | 0 | nullptr, nullptr)); |
1266 | 0 | if (!data->m_poTrimmingDS) |
1267 | 0 | return CE_Failure; |
1268 | 0 | if (data->m_poTrimmingDS->GetRasterCount() != 1) |
1269 | 0 | { |
1270 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
1271 | 0 | "Trimming dataset should have a single band"); |
1272 | 0 | return CE_Failure; |
1273 | 0 | } |
1274 | 0 | data->m_poTrimmingBand = data->m_poTrimmingDS->GetRasterBand(1); |
1275 | |
|
1276 | 0 | double adfAuxGT[6]; |
1277 | 0 | if (data->m_poTrimmingDS->GetGeoTransform(adfAuxGT) != CE_None) |
1278 | 0 | { |
1279 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "%s lacks a geotransform", |
1280 | 0 | osFilename.c_str()); |
1281 | 0 | return CE_Failure; |
1282 | 0 | } |
1283 | 0 | int bAuxBandHasNoData = false; |
1284 | 0 | const double dfAuxNoData = |
1285 | 0 | data->m_poTrimmingBand->GetNoDataValue(&bAuxBandHasNoData); |
1286 | 0 | if (!bTrimmingNodataSpecified && bAuxBandHasNoData) |
1287 | 0 | data->m_dfTrimmingNodata = dfAuxNoData; |
1288 | |
|
1289 | 0 | SetOutputValuesForInNoDataAndOutNoData( |
1290 | 0 | nInBands, padfInNoData, pnOutBands, ppadfOutNoData, bNodataSpecified, |
1291 | 0 | dfNoData, bNodataSpecified, dfNoData, bIsFinalStep); |
1292 | |
|
1293 | 0 | *ppWorkingData = data.release(); |
1294 | 0 | return CE_None; |
1295 | 0 | } |
1296 | | |
1297 | | /************************************************************************/ |
1298 | | /* TrimmingFree() */ |
1299 | | /************************************************************************/ |
1300 | | |
1301 | | /** Free function for 'Trimming' builtin function. */ |
1302 | | static void TrimmingFree(const char * /*pszFuncName*/, void * /*pUserData*/, |
1303 | | VRTPDWorkingDataPtr pWorkingData) |
1304 | 0 | { |
1305 | 0 | TrimmingData *data = static_cast<TrimmingData *>(pWorkingData); |
1306 | 0 | CPLAssert(data->m_osSignature == TrimmingData::EXPECTED_SIGNATURE); |
1307 | 0 | CPL_IGNORE_RET_VAL(data->m_osSignature); |
1308 | 0 | delete data; |
1309 | 0 | } |
1310 | | |
1311 | | /************************************************************************/ |
1312 | | /* TrimmingProcess() */ |
1313 | | /************************************************************************/ |
1314 | | |
1315 | | /** Processing function for 'Trimming' builtin function. */ |
1316 | | static CPLErr TrimmingProcess( |
1317 | | const char * /*pszFuncName*/, void * /*pUserData*/, |
1318 | | VRTPDWorkingDataPtr pWorkingData, CSLConstList /* papszFunctionArgs*/, |
1319 | | int nBufXSize, int nBufYSize, const void *pInBuffer, size_t nInBufferSize, |
1320 | | GDALDataType eInDT, int nInBands, const double *CPL_RESTRICT padfInNoData, |
1321 | | void *pOutBuffer, size_t nOutBufferSize, GDALDataType eOutDT, int nOutBands, |
1322 | | const double *CPL_RESTRICT padfOutNoData, double dfSrcXOff, |
1323 | | double dfSrcYOff, double dfSrcXSize, double dfSrcYSize, |
1324 | | const double adfSrcGT[], const char * /* pszVRTPath */, |
1325 | | CSLConstList /*papszExtra*/) |
1326 | 0 | { |
1327 | 0 | const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize; |
1328 | |
|
1329 | 0 | CPL_IGNORE_RET_VAL(eInDT); |
1330 | 0 | CPLAssert(eInDT == GDT_Float64); |
1331 | 0 | CPL_IGNORE_RET_VAL(eOutDT); |
1332 | 0 | CPLAssert(eOutDT == GDT_Float64); |
1333 | 0 | CPL_IGNORE_RET_VAL(nInBufferSize); |
1334 | 0 | CPLAssert(nInBufferSize == nElts * nInBands * sizeof(double)); |
1335 | 0 | CPL_IGNORE_RET_VAL(nOutBufferSize); |
1336 | 0 | CPLAssert(nOutBufferSize == nElts * nOutBands * sizeof(double)); |
1337 | 0 | CPLAssert(nInBands == nOutBands); |
1338 | 0 | CPL_IGNORE_RET_VAL(nOutBands); |
1339 | |
|
1340 | 0 | TrimmingData *data = static_cast<TrimmingData *>(pWorkingData); |
1341 | 0 | CPLAssert(data->m_osSignature == TrimmingData::EXPECTED_SIGNATURE); |
1342 | 0 | const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer); |
1343 | 0 | double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer); |
1344 | | |
1345 | | // Compute georeferenced extent of input region |
1346 | 0 | const double dfULX = |
1347 | 0 | adfSrcGT[0] + adfSrcGT[1] * dfSrcXOff + adfSrcGT[2] * dfSrcYOff; |
1348 | 0 | const double dfULY = |
1349 | 0 | adfSrcGT[3] + adfSrcGT[4] * dfSrcXOff + adfSrcGT[5] * dfSrcYOff; |
1350 | 0 | const double dfLRX = adfSrcGT[0] + adfSrcGT[1] * (dfSrcXOff + dfSrcXSize) + |
1351 | 0 | adfSrcGT[2] * (dfSrcYOff + dfSrcYSize); |
1352 | 0 | const double dfLRY = adfSrcGT[3] + adfSrcGT[4] * (dfSrcXOff + dfSrcXSize) + |
1353 | 0 | adfSrcGT[5] * (dfSrcYOff + dfSrcYSize); |
1354 | |
|
1355 | 0 | if (!LoadAuxData(dfULX, dfULY, dfLRX, dfLRY, nElts, nBufXSize, nBufYSize, |
1356 | 0 | "trimming", data->m_poTrimmingBand, |
1357 | 0 | data->m_abyTrimmingBuffer)) |
1358 | 0 | { |
1359 | 0 | return CE_Failure; |
1360 | 0 | } |
1361 | | |
1362 | 0 | const float *pafTrimming = |
1363 | 0 | reinterpret_cast<const float *>(data->m_abyTrimmingBuffer.data()); |
1364 | 0 | const int nRedBand = data->m_nRedBand; |
1365 | 0 | const int nGreenBand = data->m_nGreenBand; |
1366 | 0 | const int nBlueBand = data->m_nBlueBand; |
1367 | 0 | const double dfTopMargin = data->m_dfTopMargin; |
1368 | 0 | const double dfTopRGB = data->m_dfTopRGB; |
1369 | 0 | const double dfToneCeil = data->m_dfToneCeil; |
1370 | 0 | #if !defined(trimming_non_optimized_version) |
1371 | 0 | const double dfInvToneCeil = 1.0 / dfToneCeil; |
1372 | 0 | #endif |
1373 | 0 | const bool bRGBBandsAreFirst = |
1374 | 0 | std::max(std::max(nRedBand, nGreenBand), nBlueBand) <= 2; |
1375 | 0 | const double dfNoDataTrimming = data->m_dfTrimmingNodata; |
1376 | 0 | const double dfNoDataRed = padfInNoData[nRedBand]; |
1377 | 0 | const double dfNoDataGreen = padfInNoData[nGreenBand]; |
1378 | 0 | const double dfNoDataBlue = padfInNoData[nBlueBand]; |
1379 | 0 | for (size_t i = 0; i < nElts; ++i) |
1380 | 0 | { |
1381 | | // Extract local saturation value from trimming image |
1382 | 0 | const double dfLocalMaxRGB = pafTrimming[i]; |
1383 | 0 | const double dfReducedRGB = |
1384 | 0 | std::min((1.0 - dfTopMargin) * dfTopRGB / dfLocalMaxRGB, 1.0); |
1385 | |
|
1386 | 0 | const double dfRed = padfSrc[nRedBand]; |
1387 | 0 | const double dfGreen = padfSrc[nGreenBand]; |
1388 | 0 | const double dfBlue = padfSrc[nBlueBand]; |
1389 | 0 | bool bNoDataPixel = false; |
1390 | 0 | if ((dfLocalMaxRGB != dfNoDataTrimming) && (dfRed != dfNoDataRed) && |
1391 | 0 | (dfGreen != dfNoDataGreen) && (dfBlue != dfNoDataBlue)) |
1392 | 0 | { |
1393 | | // RGB bands specific process |
1394 | 0 | const double dfMaxRGB = std::max(std::max(dfRed, dfGreen), dfBlue); |
1395 | 0 | #if !defined(trimming_non_optimized_version) |
1396 | 0 | const double dfRedTimesToneRed = std::min(dfRed, dfToneCeil); |
1397 | 0 | const double dfGreenTimesToneGreen = std::min(dfGreen, dfToneCeil); |
1398 | 0 | const double dfBlueTimesToneBlue = std::min(dfBlue, dfToneCeil); |
1399 | 0 | const double dfInvToneMaxRGB = |
1400 | 0 | std::max(dfMaxRGB * dfInvToneCeil, 1.0); |
1401 | 0 | const double dfReducedRGBTimesInvToneMaxRGB = |
1402 | 0 | dfReducedRGB * dfInvToneMaxRGB; |
1403 | 0 | padfDst[nRedBand] = std::min( |
1404 | 0 | dfRedTimesToneRed * dfReducedRGBTimesInvToneMaxRGB, dfTopRGB); |
1405 | 0 | padfDst[nGreenBand] = |
1406 | 0 | std::min(dfGreenTimesToneGreen * dfReducedRGBTimesInvToneMaxRGB, |
1407 | 0 | dfTopRGB); |
1408 | 0 | padfDst[nBlueBand] = std::min( |
1409 | 0 | dfBlueTimesToneBlue * dfReducedRGBTimesInvToneMaxRGB, dfTopRGB); |
1410 | | #else |
1411 | | // Original formulas. Slightly less optimized than the above ones. |
1412 | | const double dfToneMaxRGB = std::min(dfToneCeil / dfMaxRGB, 1.0); |
1413 | | const double dfToneRed = std::min(dfToneCeil / dfRed, 1.0); |
1414 | | const double dfToneGreen = std::min(dfToneCeil / dfGreen, 1.0); |
1415 | | const double dfToneBlue = std::min(dfToneCeil / dfBlue, 1.0); |
1416 | | padfDst[nRedBand] = std::min( |
1417 | | dfReducedRGB * dfRed * dfToneRed / dfToneMaxRGB, dfTopRGB); |
1418 | | padfDst[nGreenBand] = std::min( |
1419 | | dfReducedRGB * dfGreen * dfToneGreen / dfToneMaxRGB, dfTopRGB); |
1420 | | padfDst[nBlueBand] = std::min( |
1421 | | dfReducedRGB * dfBlue * dfToneBlue / dfToneMaxRGB, dfTopRGB); |
1422 | | #endif |
1423 | | |
1424 | | // Other bands processing (NIR, ...): only apply RGB reduction factor |
1425 | 0 | if (bRGBBandsAreFirst) |
1426 | 0 | { |
1427 | | // optimization |
1428 | 0 | for (int iBand = 3; iBand < nInBands; ++iBand) |
1429 | 0 | { |
1430 | 0 | if (padfSrc[iBand] != padfInNoData[iBand]) |
1431 | 0 | { |
1432 | 0 | padfDst[iBand] = dfReducedRGB * padfSrc[iBand]; |
1433 | 0 | } |
1434 | 0 | else |
1435 | 0 | { |
1436 | 0 | bNoDataPixel = true; |
1437 | 0 | break; |
1438 | 0 | } |
1439 | 0 | } |
1440 | 0 | } |
1441 | 0 | else |
1442 | 0 | { |
1443 | 0 | for (int iBand = 0; iBand < nInBands; ++iBand) |
1444 | 0 | { |
1445 | 0 | if (iBand != nRedBand && iBand != nGreenBand && |
1446 | 0 | iBand != nBlueBand) |
1447 | 0 | { |
1448 | 0 | if (padfSrc[iBand] != padfInNoData[iBand]) |
1449 | 0 | { |
1450 | 0 | padfDst[iBand] = dfReducedRGB * padfSrc[iBand]; |
1451 | 0 | } |
1452 | 0 | else |
1453 | 0 | { |
1454 | 0 | bNoDataPixel = true; |
1455 | 0 | break; |
1456 | 0 | } |
1457 | 0 | } |
1458 | 0 | } |
1459 | 0 | } |
1460 | 0 | } |
1461 | 0 | else |
1462 | 0 | { |
1463 | 0 | bNoDataPixel = true; |
1464 | 0 | } |
1465 | 0 | if (bNoDataPixel) |
1466 | 0 | { |
1467 | 0 | for (int iBand = 0; iBand < nInBands; ++iBand) |
1468 | 0 | { |
1469 | 0 | padfDst[iBand] = padfOutNoData[iBand]; |
1470 | 0 | } |
1471 | 0 | } |
1472 | |
|
1473 | 0 | padfSrc += nInBands; |
1474 | 0 | padfDst += nInBands; |
1475 | 0 | } |
1476 | |
|
1477 | 0 | return CE_None; |
1478 | 0 | } |
1479 | | |
1480 | | /************************************************************************/ |
1481 | | /* ExpressionInit() */ |
1482 | | /************************************************************************/ |
1483 | | |
1484 | | namespace |
1485 | | { |
1486 | | |
1487 | | class ExpressionData |
1488 | | { |
1489 | | public: |
1490 | | ExpressionData(int nInBands, int nBatchSize, std::string_view osExpression, |
1491 | | std::string_view osDialect) |
1492 | 0 | : m_nInBands(nInBands), m_nNominalBatchSize(nBatchSize), |
1493 | 0 | m_nBatchCount(DIV_ROUND_UP(nInBands, nBatchSize)), m_adfResults{}, |
1494 | 0 | m_osExpression(std::string(osExpression)), |
1495 | 0 | m_osDialect(std::string(osDialect)), m_oNominalBatchEnv{}, |
1496 | 0 | m_oPartialBatchEnv{} |
1497 | 0 | { |
1498 | 0 | } |
1499 | | |
1500 | | CPLErr Compile() |
1501 | 0 | { |
1502 | 0 | auto eErr = m_oNominalBatchEnv.Initialize(m_osExpression, m_osDialect, |
1503 | 0 | m_nNominalBatchSize); |
1504 | 0 | if (eErr != CE_None) |
1505 | 0 | { |
1506 | 0 | return eErr; |
1507 | 0 | } |
1508 | | |
1509 | 0 | const auto nPartialBatchSize = m_nInBands % m_nNominalBatchSize; |
1510 | 0 | if (nPartialBatchSize) |
1511 | 0 | { |
1512 | 0 | eErr = m_oPartialBatchEnv.Initialize(m_osExpression, m_osDialect, |
1513 | 0 | nPartialBatchSize); |
1514 | 0 | } |
1515 | |
|
1516 | 0 | return eErr; |
1517 | 0 | } |
1518 | | |
1519 | | CPLErr Evaluate(const double *padfInputs, size_t nExpectedOutBands) |
1520 | 0 | { |
1521 | 0 | m_adfResults.clear(); |
1522 | |
|
1523 | 0 | for (int iBatch = 0; iBatch < m_nBatchCount; iBatch++) |
1524 | 0 | { |
1525 | 0 | const auto nBandsRemaining = |
1526 | 0 | static_cast<int>(m_nInBands - (m_nNominalBatchSize * iBatch)); |
1527 | 0 | const auto nBatchSize = |
1528 | 0 | std::min(m_nNominalBatchSize, nBandsRemaining); |
1529 | |
|
1530 | 0 | auto &oEnv = GetEnv(nBatchSize); |
1531 | |
|
1532 | 0 | const double *pdfStart = padfInputs + iBatch * m_nNominalBatchSize; |
1533 | 0 | const double *pdfEnd = pdfStart + nBatchSize; |
1534 | |
|
1535 | 0 | std::copy(pdfStart, pdfEnd, oEnv.m_adfValuesForPixel.begin()); |
1536 | |
|
1537 | 0 | if (auto eErr = oEnv.m_poExpression->Evaluate(); eErr != CE_None) |
1538 | 0 | { |
1539 | 0 | return eErr; |
1540 | 0 | } |
1541 | | |
1542 | 0 | const auto &adfResults = oEnv.m_poExpression->Results(); |
1543 | 0 | if (m_nBatchCount > 1) |
1544 | 0 | { |
1545 | 0 | std::copy(adfResults.begin(), adfResults.end(), |
1546 | 0 | std::back_inserter(m_adfResults)); |
1547 | 0 | } |
1548 | 0 | } |
1549 | | |
1550 | 0 | if (nExpectedOutBands > 0) |
1551 | 0 | { |
1552 | 0 | if (Results().size() != static_cast<std::size_t>(nExpectedOutBands)) |
1553 | 0 | { |
1554 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1555 | 0 | "Expression returned %d values but " |
1556 | 0 | "%d output bands were expected.", |
1557 | 0 | static_cast<int>(Results().size()), |
1558 | 0 | static_cast<int>(nExpectedOutBands)); |
1559 | 0 | return CE_Failure; |
1560 | 0 | } |
1561 | 0 | } |
1562 | | |
1563 | 0 | return CE_None; |
1564 | 0 | } |
1565 | | |
1566 | | const std::vector<double> &Results() const |
1567 | 0 | { |
1568 | 0 | if (m_nBatchCount == 1) |
1569 | 0 | { |
1570 | 0 | return m_oNominalBatchEnv.m_poExpression->Results(); |
1571 | 0 | } |
1572 | 0 | else |
1573 | 0 | { |
1574 | 0 | return m_adfResults; |
1575 | 0 | } |
1576 | 0 | } |
1577 | | |
1578 | | private: |
1579 | | const int m_nInBands; |
1580 | | const int m_nNominalBatchSize; |
1581 | | const int m_nBatchCount; |
1582 | | std::vector<double> m_adfResults; |
1583 | | |
1584 | | const CPLString m_osExpression; |
1585 | | const CPLString m_osDialect; |
1586 | | |
1587 | | struct InvocationEnv |
1588 | | { |
1589 | | std::vector<double> m_adfValuesForPixel; |
1590 | | std::unique_ptr<gdal::MathExpression> m_poExpression; |
1591 | | |
1592 | | CPLErr Initialize(const CPLString &osExpression, |
1593 | | const CPLString &osDialect, int nBatchSize) |
1594 | 0 | { |
1595 | 0 | m_poExpression = |
1596 | 0 | gdal::MathExpression::Create(osExpression, osDialect.c_str()); |
1597 | | // cppcheck-suppress knownConditionTrueFalse |
1598 | 0 | if (m_poExpression == nullptr) |
1599 | 0 | { |
1600 | 0 | return CE_Failure; |
1601 | 0 | } |
1602 | | |
1603 | 0 | m_adfValuesForPixel.resize(nBatchSize); |
1604 | |
|
1605 | 0 | for (int i = 0; i < nBatchSize; i++) |
1606 | 0 | { |
1607 | 0 | std::string osVar = "B" + std::to_string(i + 1); |
1608 | 0 | m_poExpression->RegisterVariable(osVar, |
1609 | 0 | &m_adfValuesForPixel[i]); |
1610 | 0 | } |
1611 | |
|
1612 | 0 | if (osExpression.ifind("BANDS") != std::string::npos) |
1613 | 0 | { |
1614 | 0 | m_poExpression->RegisterVector("BANDS", &m_adfValuesForPixel); |
1615 | 0 | } |
1616 | |
|
1617 | 0 | return m_poExpression->Compile(); |
1618 | 0 | } |
1619 | | }; |
1620 | | |
1621 | | InvocationEnv &GetEnv(int nBatchSize) |
1622 | 0 | { |
1623 | 0 | if (nBatchSize == m_nNominalBatchSize) |
1624 | 0 | { |
1625 | 0 | return m_oNominalBatchEnv; |
1626 | 0 | } |
1627 | 0 | else |
1628 | 0 | { |
1629 | 0 | return m_oPartialBatchEnv; |
1630 | 0 | } |
1631 | 0 | } |
1632 | | |
1633 | | InvocationEnv m_oNominalBatchEnv; |
1634 | | InvocationEnv m_oPartialBatchEnv; |
1635 | | }; |
1636 | | |
1637 | | } // namespace |
1638 | | |
1639 | | static CPLErr ExpressionInit(const char * /*pszFuncName*/, void * /*pUserData*/, |
1640 | | CSLConstList papszFunctionArgs, int nInBands, |
1641 | | GDALDataType eInDT, double * /* padfInNoData */, |
1642 | | int *pnOutBands, GDALDataType *peOutDT, |
1643 | | double ** /* ppadfOutNoData */, |
1644 | | const char * /* pszVRTPath */, |
1645 | | VRTPDWorkingDataPtr *ppWorkingData) |
1646 | 0 | { |
1647 | 0 | CPLAssert(eInDT == GDT_Float64); |
1648 | | |
1649 | 0 | *peOutDT = eInDT; |
1650 | 0 | *ppWorkingData = nullptr; |
1651 | |
|
1652 | 0 | const char *pszBatchSize = |
1653 | 0 | CSLFetchNameValue(papszFunctionArgs, "batch_size"); |
1654 | 0 | auto nBatchSize = nInBands; |
1655 | |
|
1656 | 0 | if (pszBatchSize != nullptr) |
1657 | 0 | { |
1658 | 0 | nBatchSize = std::min(nInBands, std::atoi(pszBatchSize)); |
1659 | 0 | } |
1660 | |
|
1661 | 0 | if (nBatchSize < 1) |
1662 | 0 | { |
1663 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, "batch_size must be at least 1"); |
1664 | 0 | return CE_Failure; |
1665 | 0 | } |
1666 | | |
1667 | 0 | const char *pszDialect = CSLFetchNameValue(papszFunctionArgs, "dialect"); |
1668 | 0 | if (pszDialect == nullptr) |
1669 | 0 | { |
1670 | 0 | pszDialect = "muparser"; |
1671 | 0 | } |
1672 | |
|
1673 | 0 | const char *pszExpression = |
1674 | 0 | CSLFetchNameValue(papszFunctionArgs, "expression"); |
1675 | |
|
1676 | 0 | auto data = std::make_unique<ExpressionData>(nInBands, nBatchSize, |
1677 | 0 | pszExpression, pszDialect); |
1678 | |
|
1679 | 0 | if (auto eErr = data->Compile(); eErr != CE_None) |
1680 | 0 | { |
1681 | 0 | return eErr; |
1682 | 0 | } |
1683 | | |
1684 | 0 | if (*pnOutBands == 0) |
1685 | 0 | { |
1686 | 0 | std::vector<double> aDummyValues(nInBands); |
1687 | 0 | if (auto eErr = data->Evaluate(aDummyValues.data(), 0); eErr != CE_None) |
1688 | 0 | { |
1689 | 0 | return eErr; |
1690 | 0 | } |
1691 | | |
1692 | 0 | *pnOutBands = static_cast<int>(data->Results().size()); |
1693 | 0 | } |
1694 | | |
1695 | 0 | *ppWorkingData = data.release(); |
1696 | |
|
1697 | 0 | return CE_None; |
1698 | 0 | } |
1699 | | |
1700 | | static void ExpressionFree(const char * /* pszFuncName */, |
1701 | | void * /* pUserData */, |
1702 | | VRTPDWorkingDataPtr pWorkingData) |
1703 | 0 | { |
1704 | 0 | ExpressionData *data = static_cast<ExpressionData *>(pWorkingData); |
1705 | 0 | delete data; |
1706 | 0 | } |
1707 | | |
1708 | | static CPLErr ExpressionProcess( |
1709 | | const char * /* pszFuncName */, void * /* pUserData */, |
1710 | | VRTPDWorkingDataPtr pWorkingData, CSLConstList /* papszFunctionArgs */, |
1711 | | int nBufXSize, int nBufYSize, const void *pInBuffer, |
1712 | | size_t /* nInBufferSize */, GDALDataType eInDT, int nInBands, |
1713 | | const double *CPL_RESTRICT /* padfInNoData */, void *pOutBuffer, |
1714 | | size_t /* nOutBufferSize */, GDALDataType eOutDT, int nOutBands, |
1715 | | const double *CPL_RESTRICT /* padfOutNoData */, double /* dfSrcXOff */, |
1716 | | double /* dfSrcYOff */, double /* dfSrcXSize */, double /* dfSrcYSize */, |
1717 | | const double /* adfSrcGT */[], const char * /* pszVRTPath "*/, |
1718 | | CSLConstList /* papszExtra */) |
1719 | 0 | { |
1720 | 0 | ExpressionData *expr = static_cast<ExpressionData *>(pWorkingData); |
1721 | |
|
1722 | 0 | const size_t nElts = static_cast<size_t>(nBufXSize) * nBufYSize; |
1723 | |
|
1724 | 0 | CPL_IGNORE_RET_VAL(eInDT); |
1725 | 0 | CPLAssert(eInDT == GDT_Float64); |
1726 | 0 | const double *CPL_RESTRICT padfSrc = static_cast<const double *>(pInBuffer); |
1727 | |
|
1728 | 0 | CPLAssert(eOutDT == GDT_Float64); |
1729 | 0 | CPL_IGNORE_RET_VAL(eOutDT); |
1730 | 0 | double *CPL_RESTRICT padfDst = static_cast<double *>(pOutBuffer); |
1731 | |
|
1732 | 0 | for (size_t i = 0; i < nElts; i++) |
1733 | 0 | { |
1734 | 0 | if (auto eErr = expr->Evaluate(padfSrc, nOutBands); eErr != CE_None) |
1735 | 0 | { |
1736 | 0 | return eErr; |
1737 | 0 | } |
1738 | | |
1739 | 0 | const auto &adfResults = expr->Results(); |
1740 | 0 | std::copy(adfResults.begin(), adfResults.end(), padfDst); |
1741 | |
|
1742 | 0 | padfDst += nOutBands; |
1743 | 0 | padfSrc += nInBands; |
1744 | 0 | } |
1745 | | |
1746 | 0 | return CE_None; |
1747 | 0 | } |
1748 | | |
1749 | | /************************************************************************/ |
1750 | | /* GDALVRTRegisterDefaultProcessedDatasetFuncs() */ |
1751 | | /************************************************************************/ |
1752 | | |
1753 | | /** Register builtin functions that can be used in a VRTProcessedDataset. |
1754 | | */ |
1755 | | void GDALVRTRegisterDefaultProcessedDatasetFuncs() |
1756 | 0 | { |
1757 | 0 | GDALVRTRegisterProcessedDatasetFunc( |
1758 | 0 | "BandAffineCombination", nullptr, |
1759 | 0 | "<ProcessedDatasetFunctionArgumentsList>" |
1760 | 0 | " <Argument name='src_nodata' type='double' " |
1761 | 0 | "description='Override input nodata value'/>" |
1762 | 0 | " <Argument name='dst_nodata' type='double' " |
1763 | 0 | "description='Override output nodata value'/>" |
1764 | 0 | " <Argument name='replacement_nodata' " |
1765 | 0 | "description='value to substitute to a valid computed value that " |
1766 | 0 | "would be nodata' type='double'/>" |
1767 | 0 | " <Argument name='dst_intended_datatype' type='string' " |
1768 | 0 | "description='Intented datatype of output (which might be " |
1769 | 0 | "different than the working data type)'/>" |
1770 | 0 | " <Argument name='coefficients_{band}' " |
1771 | 0 | "description='Comma-separated coefficients for combining bands. " |
1772 | 0 | "First one is constant term' " |
1773 | 0 | "type='double_list' required='true'/>" |
1774 | 0 | " <Argument name='min' description='clamp min value' type='double'/>" |
1775 | 0 | " <Argument name='max' description='clamp max value' type='double'/>" |
1776 | 0 | "</ProcessedDatasetFunctionArgumentsList>", |
1777 | 0 | GDT_Float64, nullptr, 0, nullptr, 0, BandAffineCombinationInit, |
1778 | 0 | BandAffineCombinationFree, BandAffineCombinationProcess, nullptr); |
1779 | |
|
1780 | 0 | GDALVRTRegisterProcessedDatasetFunc( |
1781 | 0 | "LUT", nullptr, |
1782 | 0 | "<ProcessedDatasetFunctionArgumentsList>" |
1783 | 0 | " <Argument name='src_nodata' type='double' " |
1784 | 0 | "description='Override input nodata value'/>" |
1785 | 0 | " <Argument name='dst_nodata' type='double' " |
1786 | 0 | "description='Override output nodata value'/>" |
1787 | 0 | " <Argument name='lut_{band}' " |
1788 | 0 | "description='List of the form [src value 1]:[dest value 1]," |
1789 | 0 | "[src value 2]:[dest value 2],...' " |
1790 | 0 | "type='string' required='true'/>" |
1791 | 0 | "</ProcessedDatasetFunctionArgumentsList>", |
1792 | 0 | GDT_Float64, nullptr, 0, nullptr, 0, LUTInit, LUTFree, LUTProcess, |
1793 | 0 | nullptr); |
1794 | |
|
1795 | 0 | GDALVRTRegisterProcessedDatasetFunc( |
1796 | 0 | "LocalScaleOffset", nullptr, |
1797 | 0 | "<ProcessedDatasetFunctionArgumentsList>" |
1798 | 0 | " <Argument name='relativeToVRT' " |
1799 | 0 | "description='Whether gain and offset filenames are relative to " |
1800 | 0 | "the VRT' type='boolean' default='false'/>" |
1801 | 0 | " <Argument name='gain_dataset_filename_{band}' " |
1802 | 0 | "description='Filename to the gain dataset' " |
1803 | 0 | "type='string' required='true'/>" |
1804 | 0 | " <Argument name='gain_dataset_band_{band}' " |
1805 | 0 | "description='Band of the gain dataset' " |
1806 | 0 | "type='integer' required='true'/>" |
1807 | 0 | " <Argument name='offset_dataset_filename_{band}' " |
1808 | 0 | "description='Filename to the offset dataset' " |
1809 | 0 | "type='string' required='true'/>" |
1810 | 0 | " <Argument name='offset_dataset_band_{band}' " |
1811 | 0 | "description='Band of the offset dataset' " |
1812 | 0 | "type='integer' required='true'/>" |
1813 | 0 | " <Argument name='min' description='clamp min value' type='double'/>" |
1814 | 0 | " <Argument name='max' description='clamp max value' type='double'/>" |
1815 | 0 | " <Argument name='nodata' type='double' " |
1816 | 0 | "description='Override dataset nodata value'/>" |
1817 | 0 | " <Argument name='gain_nodata' type='double' " |
1818 | 0 | "description='Override gain dataset nodata value'/>" |
1819 | 0 | " <Argument name='offset_nodata' type='double' " |
1820 | 0 | "description='Override offset dataset nodata value'/>" |
1821 | 0 | "</ProcessedDatasetFunctionArgumentsList>", |
1822 | 0 | GDT_Float64, nullptr, 0, nullptr, 0, LocalScaleOffsetInit, |
1823 | 0 | LocalScaleOffsetFree, LocalScaleOffsetProcess, nullptr); |
1824 | |
|
1825 | 0 | GDALVRTRegisterProcessedDatasetFunc( |
1826 | 0 | "Trimming", nullptr, |
1827 | 0 | "<ProcessedDatasetFunctionArgumentsList>" |
1828 | 0 | " <Argument name='relativeToVRT' " |
1829 | 0 | "description='Whether trimming_dataset_filename is relative to the VRT'" |
1830 | 0 | " type='boolean' default='false'/>" |
1831 | 0 | " <Argument name='trimming_dataset_filename' " |
1832 | 0 | "description='Filename to the trimming dataset' " |
1833 | 0 | "type='string' required='true'/>" |
1834 | 0 | " <Argument name='red_band' type='integer' default='1'/>" |
1835 | 0 | " <Argument name='green_band' type='integer' default='2'/>" |
1836 | 0 | " <Argument name='blue_band' type='integer' default='3'/>" |
1837 | 0 | " <Argument name='top_rgb' " |
1838 | 0 | "description='Maximum saturating RGB output value' " |
1839 | 0 | "type='double' required='true'/>" |
1840 | 0 | " <Argument name='tone_ceil' " |
1841 | 0 | "description='Maximum threshold beyond which we give up saturation' " |
1842 | 0 | "type='double' required='true'/>" |
1843 | 0 | " <Argument name='top_margin' " |
1844 | 0 | "description='Margin to allow for dynamics in brighest areas " |
1845 | 0 | "(between 0 and 1, should be close to 0)' " |
1846 | 0 | "type='double' required='true'/>" |
1847 | 0 | " <Argument name='nodata' type='double' " |
1848 | 0 | "description='Override dataset nodata value'/>" |
1849 | 0 | " <Argument name='trimming_nodata' type='double' " |
1850 | 0 | "description='Override trimming dataset nodata value'/>" |
1851 | 0 | "</ProcessedDatasetFunctionArgumentsList>", |
1852 | 0 | GDT_Float64, nullptr, 0, nullptr, 0, TrimmingInit, TrimmingFree, |
1853 | 0 | TrimmingProcess, nullptr); |
1854 | |
|
1855 | 0 | GDALVRTRegisterProcessedDatasetFunc( |
1856 | 0 | "Expression", nullptr, |
1857 | 0 | "<ProcessedDatasetFunctionArgumentsList>" |
1858 | 0 | " <Argument name='expression' description='the expression to " |
1859 | 0 | "evaluate' type='string' required='true' />" |
1860 | 0 | " <Argument name='dialect' description='expression dialect' " |
1861 | 0 | "type='string' />" |
1862 | 0 | " <Argument name='batch_size' description='batch size' " |
1863 | 0 | "type='integer' />" |
1864 | 0 | "</ProcessedDatasetFunctionArgumentsList>", |
1865 | 0 | GDT_Float64, nullptr, 0, nullptr, 0, ExpressionInit, ExpressionFree, |
1866 | 0 | ExpressionProcess, nullptr); |
1867 | 0 | } |