Coverage Report

Created: 2025-06-13 06:18

/src/gdal/alg/gdal_tps.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  High Performance Image Reprojector
4
 * Purpose:  Thin Plate Spline transformer (GDAL wrapper portion)
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2004, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "thinplatespline.h"
16
17
#include <stdlib.h>
18
#include <string.h>
19
#include <map>
20
#include <utility>
21
22
#include "cpl_atomic_ops.h"
23
#include "cpl_conv.h"
24
#include "cpl_error.h"
25
#include "cpl_minixml.h"
26
#include "cpl_multiproc.h"
27
#include "cpl_string.h"
28
#include "gdal.h"
29
#include "gdal_alg.h"
30
#include "gdal_alg_priv.h"
31
#include "gdal_priv.h"
32
#include "gdalgenericinverse.h"
33
34
CPL_C_START
35
CPLXMLNode *GDALSerializeTPSTransformer(void *pTransformArg);
36
void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree);
37
CPL_C_END
38
39
struct TPSTransformInfo
40
{
41
    GDALTransformerInfo sTI{};
42
43
    VizGeorefSpline2D *poForward{};
44
    VizGeorefSpline2D *poReverse{};
45
    bool bForwardSolved{};
46
    bool bReverseSolved{};
47
    double dfSrcApproxErrorReverse{};
48
49
    bool bReversed{};
50
51
    std::vector<gdal::GCP> asGCPs{};
52
53
    volatile int nRefCount{};
54
};
55
56
/************************************************************************/
57
/*                   GDALCreateSimilarTPSTransformer()                  */
58
/************************************************************************/
59
60
static void *GDALCreateSimilarTPSTransformer(void *hTransformArg,
61
                                             double dfRatioX, double dfRatioY)
62
0
{
63
0
    VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarTPSTransformer",
64
0
                      nullptr);
65
66
0
    TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(hTransformArg);
67
68
0
    if (dfRatioX == 1.0 && dfRatioY == 1.0)
69
0
    {
70
        // We can just use a ref count, since using the source transformation
71
        // is thread-safe.
72
0
        CPLAtomicInc(&(psInfo->nRefCount));
73
0
    }
74
0
    else
75
0
    {
76
0
        auto newGCPs = psInfo->asGCPs;
77
0
        for (auto &gcp : newGCPs)
78
0
        {
79
0
            gcp.Pixel() /= dfRatioX;
80
0
            gcp.Line() /= dfRatioY;
81
0
        }
82
0
        psInfo = static_cast<TPSTransformInfo *>(GDALCreateTPSTransformer(
83
0
            static_cast<int>(newGCPs.size()), gdal::GCP::c_ptr(newGCPs),
84
0
            psInfo->bReversed));
85
0
    }
86
87
0
    return psInfo;
88
0
}
89
90
/************************************************************************/
91
/*                      GDALCreateTPSTransformer()                      */
92
/************************************************************************/
93
94
/**
95
 * Create Thin Plate Spline transformer from GCPs.
96
 *
97
 * The thin plate spline transformer produces exact transformation
98
 * at all control points and smoothly varying transformations between
99
 * control points with greatest influence from local control points.
100
 * It is suitable for for many applications not well modeled by polynomial
101
 * transformations.
102
 *
103
 * Creating the TPS transformer involves solving systems of linear equations
104
 * related to the number of control points involved.  This solution is
105
 * computed within this function call.  It can be quite an expensive operation
106
 * for large numbers of GCPs.  For instance, for reference, it takes on the
107
 * order of 10s for 400 GCPs on a 2GHz Athlon processor.
108
 *
109
 * TPS Transformers are serializable.
110
 *
111
 * The GDAL Thin Plate Spline transformer is based on code provided by
112
 * Gilad Ronnen on behalf of VIZRT Inc (http://www.visrt.com).  Incorporation
113
 * of the algorithm into GDAL was supported by the Centro di Ecologia Alpina
114
 * (http://www.cealp.it).
115
 *
116
 * @param nGCPCount the number of GCPs in pasGCPList.
117
 * @param pasGCPList an array of GCPs to be used as input.
118
 * @param bReversed set it to TRUE to compute the reversed transformation.
119
 *
120
 * @return the transform argument or NULL if creation fails.
121
 */
122
123
void *GDALCreateTPSTransformer(int nGCPCount, const GDAL_GCP *pasGCPList,
124
                               int bReversed)
125
0
{
126
0
    return GDALCreateTPSTransformerInt(nGCPCount, pasGCPList, bReversed,
127
0
                                       nullptr);
128
0
}
129
130
static void GDALTPSComputeForwardInThread(void *pData)
131
0
{
132
0
    TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(pData);
133
0
    psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
134
0
}
135
136
void *GDALCreateTPSTransformerInt(int nGCPCount, const GDAL_GCP *pasGCPList,
137
                                  int bReversed, CSLConstList papszOptions)
138
139
0
{
140
    /* -------------------------------------------------------------------- */
141
    /*      Allocate transform info.                                        */
142
    /* -------------------------------------------------------------------- */
143
0
    TPSTransformInfo *psInfo = new TPSTransformInfo();
144
145
0
    psInfo->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount);
146
147
0
    psInfo->bReversed = CPL_TO_BOOL(bReversed);
148
0
    psInfo->poForward = new VizGeorefSpline2D(2);
149
0
    psInfo->poReverse = new VizGeorefSpline2D(2);
150
151
0
    memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
152
0
           strlen(GDAL_GTI2_SIGNATURE));
153
0
    psInfo->sTI.pszClassName = "GDALTPSTransformer";
154
0
    psInfo->sTI.pfnTransform = GDALTPSTransform;
155
0
    psInfo->sTI.pfnCleanup = GDALDestroyTPSTransformer;
156
0
    psInfo->sTI.pfnSerialize = GDALSerializeTPSTransformer;
157
0
    psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarTPSTransformer;
158
159
    /* -------------------------------------------------------------------- */
160
    /*      Attach (non-redundant) points to the transformation.            */
161
    /* -------------------------------------------------------------------- */
162
0
    std::map<std::pair<double, double>, int> oMapPixelLineToIdx;
163
0
    std::map<std::pair<double, double>, int> oMapXYToIdx;
164
0
    for (int iGCP = 0; iGCP < nGCPCount; iGCP++)
165
0
    {
166
0
        const double afPL[2] = {pasGCPList[iGCP].dfGCPPixel,
167
0
                                pasGCPList[iGCP].dfGCPLine};
168
0
        const double afXY[2] = {pasGCPList[iGCP].dfGCPX,
169
0
                                pasGCPList[iGCP].dfGCPY};
170
171
0
        std::map<std::pair<double, double>, int>::iterator oIter(
172
0
            oMapPixelLineToIdx.find(
173
0
                std::pair<double, double>(afPL[0], afPL[1])));
174
0
        if (oIter != oMapPixelLineToIdx.end())
175
0
        {
176
0
            if (afXY[0] == pasGCPList[oIter->second].dfGCPX &&
177
0
                afXY[1] == pasGCPList[oIter->second].dfGCPY)
178
0
            {
179
0
                continue;
180
0
            }
181
0
            else
182
0
            {
183
0
                CPLError(CE_Warning, CPLE_AppDefined,
184
0
                         "GCP %d and %d have same (pixel,line)=(%f,%f), "
185
0
                         "but different (X,Y): (%f,%f) vs (%f,%f)",
186
0
                         iGCP + 1, oIter->second, afPL[0], afPL[1], afXY[0],
187
0
                         afXY[1], pasGCPList[oIter->second].dfGCPX,
188
0
                         pasGCPList[oIter->second].dfGCPY);
189
0
            }
190
0
        }
191
0
        else
192
0
        {
193
0
            oMapPixelLineToIdx[std::pair<double, double>(afPL[0], afPL[1])] =
194
0
                iGCP;
195
0
        }
196
197
0
        oIter = oMapXYToIdx.find(std::pair<double, double>(afXY[0], afXY[1]));
198
0
        if (oIter != oMapXYToIdx.end())
199
0
        {
200
0
            CPLError(CE_Warning, CPLE_AppDefined,
201
0
                     "GCP %d and %d have same (x,y)=(%f,%f), "
202
0
                     "but different (pixel,line): (%f,%f) vs (%f,%f)",
203
0
                     iGCP + 1, oIter->second, afXY[0], afXY[1], afPL[0],
204
0
                     afPL[1], pasGCPList[oIter->second].dfGCPPixel,
205
0
                     pasGCPList[oIter->second].dfGCPLine);
206
0
        }
207
0
        else
208
0
        {
209
0
            oMapXYToIdx[std::pair<double, double>(afXY[0], afXY[1])] = iGCP;
210
0
        }
211
212
0
        bool bOK = true;
213
0
        if (bReversed)
214
0
        {
215
0
            bOK &= psInfo->poReverse->add_point(afPL[0], afPL[1], afXY);
216
0
            bOK &= psInfo->poForward->add_point(afXY[0], afXY[1], afPL);
217
0
        }
218
0
        else
219
0
        {
220
0
            bOK &= psInfo->poForward->add_point(afPL[0], afPL[1], afXY);
221
0
            bOK &= psInfo->poReverse->add_point(afXY[0], afXY[1], afPL);
222
0
        }
223
0
        if (!bOK)
224
0
        {
225
0
            GDALDestroyTPSTransformer(psInfo);
226
0
            return nullptr;
227
0
        }
228
0
    }
229
230
0
    psInfo->nRefCount = 1;
231
232
0
    psInfo->dfSrcApproxErrorReverse = CPLAtof(
233
0
        CSLFetchNameValueDef(papszOptions, "SRC_APPROX_ERROR_IN_PIXEL", "0"));
234
235
0
    int nThreads = 1;
236
0
    if (nGCPCount > 100)
237
0
    {
238
0
        const char *pszWarpThreads =
239
0
            CSLFetchNameValue(papszOptions, "NUM_THREADS");
240
0
        if (pszWarpThreads == nullptr)
241
0
            pszWarpThreads = CPLGetConfigOption("GDAL_NUM_THREADS", "1");
242
0
        if (EQUAL(pszWarpThreads, "ALL_CPUS"))
243
0
            nThreads = CPLGetNumCPUs();
244
0
        else
245
0
            nThreads = atoi(pszWarpThreads);
246
0
    }
247
248
0
    if (nThreads > 1)
249
0
    {
250
        // Compute direct and reverse transforms in parallel.
251
0
        CPLJoinableThread *hThread =
252
0
            CPLCreateJoinableThread(GDALTPSComputeForwardInThread, psInfo);
253
0
        psInfo->bReverseSolved = psInfo->poReverse->solve() != 0;
254
0
        if (hThread != nullptr)
255
0
            CPLJoinThread(hThread);
256
0
        else
257
0
            psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
258
0
    }
259
0
    else
260
0
    {
261
0
        psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
262
0
        psInfo->bReverseSolved = psInfo->poReverse->solve() != 0;
263
0
    }
264
265
0
    if (!psInfo->bForwardSolved || !psInfo->bReverseSolved)
266
0
    {
267
0
        GDALDestroyTPSTransformer(psInfo);
268
0
        return nullptr;
269
0
    }
270
271
0
    return psInfo;
272
0
}
273
274
/************************************************************************/
275
/*                     GDALDestroyTPSTransformer()                      */
276
/************************************************************************/
277
278
/**
279
 * Destroy TPS transformer.
280
 *
281
 * This function is used to destroy information about a GCP based
282
 * polynomial transformation created with GDALCreateTPSTransformer().
283
 *
284
 * @param pTransformArg the transform arg previously returned by
285
 * GDALCreateTPSTransformer().
286
 */
287
288
void GDALDestroyTPSTransformer(void *pTransformArg)
289
290
0
{
291
0
    if (pTransformArg == nullptr)
292
0
        return;
293
294
0
    TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(pTransformArg);
295
296
0
    if (CPLAtomicDec(&(psInfo->nRefCount)) == 0)
297
0
    {
298
0
        delete psInfo->poForward;
299
0
        delete psInfo->poReverse;
300
301
0
        delete psInfo;
302
0
    }
303
0
}
304
305
/************************************************************************/
306
/*                          GDALTPSTransform()                          */
307
/************************************************************************/
308
309
/**
310
 * Transforms point based on GCP derived polynomial model.
311
 *
312
 * This function matches the GDALTransformerFunc signature, and can be
313
 * used to transform one or more points from pixel/line coordinates to
314
 * georeferenced coordinates (SrcToDst) or vice versa (DstToSrc).
315
 *
316
 * @param pTransformArg return value from GDALCreateTPSTransformer().
317
 * @param bDstToSrc TRUE if transformation is from the destination
318
 * (georeferenced) coordinates to pixel/line or FALSE when transforming
319
 * from pixel/line to georeferenced coordinates.
320
 * @param nPointCount the number of values in the x, y and z arrays.
321
 * @param x array containing the X values to be transformed.
322
 * @param y array containing the Y values to be transformed.
323
 * @param z array containing the Z values to be transformed.
324
 * @param panSuccess array in which a flag indicating success (TRUE) or
325
 * failure (FALSE) of the transformation are placed.
326
 *
327
 * @return TRUE if all points have been successfully transformed.
328
 */
329
330
int GDALTPSTransform(void *pTransformArg, int bDstToSrc, int nPointCount,
331
                     double *x, double *y, CPL_UNUSED double *z,
332
                     int *panSuccess)
333
0
{
334
0
    VALIDATE_POINTER1(pTransformArg, "GDALTPSTransform", 0);
335
336
0
    TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(pTransformArg);
337
338
0
    for (int i = 0; i < nPointCount; i++)
339
0
    {
340
0
        double xy_out[2] = {0.0, 0.0};
341
342
0
        if (bDstToSrc)
343
0
        {
344
            // Compute initial guess
345
0
            psInfo->poReverse->get_point(x[i], y[i], xy_out);
346
347
0
            const auto ForwardTransformer = [](double xIn, double yIn,
348
0
                                               double &xOut, double &yOut,
349
0
                                               void *pUserData)
350
0
            {
351
0
                double xyOut[2] = {0.0, 0.0};
352
0
                TPSTransformInfo *l_psInfo =
353
0
                    static_cast<TPSTransformInfo *>(pUserData);
354
0
                l_psInfo->poForward->get_point(xIn, yIn, xyOut);
355
0
                xOut = xyOut[0];
356
0
                yOut = xyOut[1];
357
0
                return true;
358
0
            };
359
360
            // Refine the initial guess
361
0
            GDALGenericInverse2D(
362
0
                x[i], y[i], xy_out[0], xy_out[1], ForwardTransformer, psInfo,
363
0
                xy_out[0], xy_out[1],
364
0
                /* computeJacobianMatrixOnlyAtFirstIter = */ true,
365
0
                /* toleranceOnOutputCoordinates = */ 0,
366
0
                psInfo->dfSrcApproxErrorReverse);
367
0
            x[i] = xy_out[0];
368
0
            y[i] = xy_out[1];
369
0
        }
370
0
        else
371
0
        {
372
0
            psInfo->poForward->get_point(x[i], y[i], xy_out);
373
0
            x[i] = xy_out[0];
374
0
            y[i] = xy_out[1];
375
0
        }
376
0
        panSuccess[i] = TRUE;
377
0
    }
378
379
0
    return TRUE;
380
0
}
381
382
/************************************************************************/
383
/*                    GDALSerializeTPSTransformer()                     */
384
/************************************************************************/
385
386
CPLXMLNode *GDALSerializeTPSTransformer(void *pTransformArg)
387
388
0
{
389
0
    VALIDATE_POINTER1(pTransformArg, "GDALSerializeTPSTransformer", nullptr);
390
391
0
    TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(pTransformArg);
392
393
0
    CPLXMLNode *psTree =
394
0
        CPLCreateXMLNode(nullptr, CXT_Element, "TPSTransformer");
395
396
    /* -------------------------------------------------------------------- */
397
    /*      Serialize bReversed.                                            */
398
    /* -------------------------------------------------------------------- */
399
0
    CPLCreateXMLElementAndValue(
400
0
        psTree, "Reversed",
401
0
        CPLString().Printf("%d", static_cast<int>(psInfo->bReversed)));
402
403
    /* -------------------------------------------------------------------- */
404
    /*      Attach GCP List.                                                */
405
    /* -------------------------------------------------------------------- */
406
0
    if (!psInfo->asGCPs.empty())
407
0
    {
408
0
        GDALSerializeGCPListToXML(psTree, psInfo->asGCPs, nullptr);
409
0
    }
410
411
0
    if (psInfo->dfSrcApproxErrorReverse > 0)
412
0
    {
413
0
        CPLCreateXMLElementAndValue(
414
0
            psTree, "SrcApproxErrorInPixel",
415
0
            CPLString().Printf("%g", psInfo->dfSrcApproxErrorReverse));
416
0
    }
417
418
0
    return psTree;
419
0
}
420
421
/************************************************************************/
422
/*                   GDALDeserializeTPSTransformer()                    */
423
/************************************************************************/
424
425
void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree)
426
427
0
{
428
    /* -------------------------------------------------------------------- */
429
    /*      Check for GCPs.                                                 */
430
    /* -------------------------------------------------------------------- */
431
0
    CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList");
432
433
0
    std::vector<gdal::GCP> asGCPs;
434
0
    if (psGCPList != nullptr)
435
0
    {
436
0
        GDALDeserializeGCPListFromXML(psGCPList, asGCPs, nullptr);
437
0
    }
438
439
    /* -------------------------------------------------------------------- */
440
    /*      Get other flags.                                                */
441
    /* -------------------------------------------------------------------- */
442
0
    const int bReversed = atoi(CPLGetXMLValue(psTree, "Reversed", "0"));
443
444
0
    CPLStringList aosOptions;
445
0
    aosOptions.SetNameValue(
446
0
        "SRC_APPROX_ERROR_IN_PIXEL",
447
0
        CPLGetXMLValue(psTree, "SrcApproxErrorInPixel", nullptr));
448
449
    /* -------------------------------------------------------------------- */
450
    /*      Generate transformation.                                        */
451
    /* -------------------------------------------------------------------- */
452
0
    void *pResult = GDALCreateTPSTransformerInt(static_cast<int>(asGCPs.size()),
453
0
                                                gdal::GCP::c_ptr(asGCPs),
454
0
                                                bReversed, aosOptions.List());
455
456
0
    return pResult;
457
0
}