LCOV - code coverage report
Current view: top level - alg - gdal_tps.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 124 140 88.6 %
Date: 2024-11-21 22:18:42 Functions: 8 9 88.9 %

          Line data    Source code
       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           3 : static void *GDALCreateSimilarTPSTransformer(void *hTransformArg,
      61             :                                              double dfRatioX, double dfRatioY)
      62             : {
      63           3 :     VALIDATE_POINTER1(hTransformArg, "GDALCreateSimilarTPSTransformer",
      64             :                       nullptr);
      65             : 
      66           3 :     TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(hTransformArg);
      67             : 
      68           3 :     if (dfRatioX == 1.0 && dfRatioY == 1.0)
      69             :     {
      70             :         // We can just use a ref count, since using the source transformation
      71             :         // is thread-safe.
      72           0 :         CPLAtomicInc(&(psInfo->nRefCount));
      73             :     }
      74             :     else
      75             :     {
      76           3 :         auto newGCPs = psInfo->asGCPs;
      77          21 :         for (auto &gcp : newGCPs)
      78             :         {
      79          18 :             gcp.Pixel() /= dfRatioX;
      80          18 :             gcp.Line() /= dfRatioY;
      81             :         }
      82           3 :         psInfo = static_cast<TPSTransformInfo *>(GDALCreateTPSTransformer(
      83           3 :             static_cast<int>(newGCPs.size()), gdal::GCP::c_ptr(newGCPs),
      84           3 :             psInfo->bReversed));
      85             :     }
      86             : 
      87           3 :     return psInfo;
      88             : }
      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           5 : void *GDALCreateTPSTransformer(int nGCPCount, const GDAL_GCP *pasGCPList,
     124             :                                int bReversed)
     125             : {
     126           5 :     return GDALCreateTPSTransformerInt(nGCPCount, pasGCPList, bReversed,
     127           5 :                                        nullptr);
     128             : }
     129             : 
     130           0 : static void GDALTPSComputeForwardInThread(void *pData)
     131             : {
     132           0 :     TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(pData);
     133           0 :     psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
     134           0 : }
     135             : 
     136          19 : void *GDALCreateTPSTransformerInt(int nGCPCount, const GDAL_GCP *pasGCPList,
     137             :                                   int bReversed, char **papszOptions)
     138             : 
     139             : {
     140             :     /* -------------------------------------------------------------------- */
     141             :     /*      Allocate transform info.                                        */
     142             :     /* -------------------------------------------------------------------- */
     143          19 :     TPSTransformInfo *psInfo = new TPSTransformInfo();
     144             : 
     145          19 :     psInfo->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount);
     146             : 
     147          19 :     psInfo->bReversed = CPL_TO_BOOL(bReversed);
     148          19 :     psInfo->poForward = new VizGeorefSpline2D(2);
     149          19 :     psInfo->poReverse = new VizGeorefSpline2D(2);
     150             : 
     151          19 :     memcpy(psInfo->sTI.abySignature, GDAL_GTI2_SIGNATURE,
     152             :            strlen(GDAL_GTI2_SIGNATURE));
     153          19 :     psInfo->sTI.pszClassName = "GDALTPSTransformer";
     154          19 :     psInfo->sTI.pfnTransform = GDALTPSTransform;
     155          19 :     psInfo->sTI.pfnCleanup = GDALDestroyTPSTransformer;
     156          19 :     psInfo->sTI.pfnSerialize = GDALSerializeTPSTransformer;
     157          19 :     psInfo->sTI.pfnCreateSimilar = GDALCreateSimilarTPSTransformer;
     158             : 
     159             :     /* -------------------------------------------------------------------- */
     160             :     /*      Attach (non-redundant) points to the transformation.            */
     161             :     /* -------------------------------------------------------------------- */
     162          38 :     std::map<std::pair<double, double>, int> oMapPixelLineToIdx;
     163          38 :     std::map<std::pair<double, double>, int> oMapXYToIdx;
     164        2426 :     for (int iGCP = 0; iGCP < nGCPCount; iGCP++)
     165             :     {
     166        2407 :         const double afPL[2] = {pasGCPList[iGCP].dfGCPPixel,
     167        2407 :                                 pasGCPList[iGCP].dfGCPLine};
     168        2407 :         const double afXY[2] = {pasGCPList[iGCP].dfGCPX,
     169        2407 :                                 pasGCPList[iGCP].dfGCPY};
     170             : 
     171             :         std::map<std::pair<double, double>, int>::iterator oIter(
     172             :             oMapPixelLineToIdx.find(
     173        2407 :                 std::pair<double, double>(afPL[0], afPL[1])));
     174        2407 :         if (oIter != oMapPixelLineToIdx.end())
     175             :         {
     176           3 :             if (afXY[0] == pasGCPList[oIter->second].dfGCPX &&
     177           1 :                 afXY[1] == pasGCPList[oIter->second].dfGCPY)
     178             :             {
     179           1 :                 continue;
     180             :             }
     181             :             else
     182             :             {
     183           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
     184             :                          "GCP %d and %d have same (pixel,line)=(%f,%f), "
     185             :                          "but different (X,Y): (%f,%f) vs (%f,%f)",
     186           1 :                          iGCP + 1, oIter->second, afPL[0], afPL[1], afXY[0],
     187           1 :                          afXY[1], pasGCPList[oIter->second].dfGCPX,
     188           1 :                          pasGCPList[oIter->second].dfGCPY);
     189             :             }
     190             :         }
     191             :         else
     192             :         {
     193        2405 :             oMapPixelLineToIdx[std::pair<double, double>(afPL[0], afPL[1])] =
     194             :                 iGCP;
     195             :         }
     196             : 
     197        2406 :         oIter = oMapXYToIdx.find(std::pair<double, double>(afXY[0], afXY[1]));
     198        2406 :         if (oIter != oMapXYToIdx.end())
     199             :         {
     200           1 :             CPLError(CE_Warning, CPLE_AppDefined,
     201             :                      "GCP %d and %d have same (x,y)=(%f,%f), "
     202             :                      "but different (pixel,line): (%f,%f) vs (%f,%f)",
     203           1 :                      iGCP + 1, oIter->second, afXY[0], afXY[1], afPL[0],
     204           1 :                      afPL[1], pasGCPList[oIter->second].dfGCPPixel,
     205           1 :                      pasGCPList[oIter->second].dfGCPLine);
     206             :         }
     207             :         else
     208             :         {
     209        2405 :             oMapXYToIdx[std::pair<double, double>(afXY[0], afXY[1])] = iGCP;
     210             :         }
     211             : 
     212        2406 :         bool bOK = true;
     213        2406 :         if (bReversed)
     214             :         {
     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             :         }
     218             :         else
     219             :         {
     220        2406 :             bOK &= psInfo->poForward->add_point(afPL[0], afPL[1], afXY);
     221        2406 :             bOK &= psInfo->poReverse->add_point(afXY[0], afXY[1], afPL);
     222             :         }
     223        2406 :         if (!bOK)
     224             :         {
     225           0 :             GDALDestroyTPSTransformer(psInfo);
     226           0 :             return nullptr;
     227             :         }
     228             :     }
     229             : 
     230          19 :     psInfo->nRefCount = 1;
     231             : 
     232          19 :     psInfo->dfSrcApproxErrorReverse = CPLAtof(
     233             :         CSLFetchNameValueDef(papszOptions, "SRC_APPROX_ERROR_IN_PIXEL", "0"));
     234             : 
     235          19 :     int nThreads = 1;
     236          19 :     if (nGCPCount > 100)
     237             :     {
     238             :         const char *pszWarpThreads =
     239           2 :             CSLFetchNameValue(papszOptions, "NUM_THREADS");
     240           2 :         if (pszWarpThreads == nullptr)
     241           2 :             pszWarpThreads = CPLGetConfigOption("GDAL_NUM_THREADS", "1");
     242           2 :         if (EQUAL(pszWarpThreads, "ALL_CPUS"))
     243           0 :             nThreads = CPLGetNumCPUs();
     244             :         else
     245           2 :             nThreads = atoi(pszWarpThreads);
     246             :     }
     247             : 
     248          19 :     if (nThreads > 1)
     249             :     {
     250             :         // Compute direct and reverse transforms in parallel.
     251             :         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             :         else
     257           0 :             psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
     258             :     }
     259             :     else
     260             :     {
     261          19 :         psInfo->bForwardSolved = psInfo->poForward->solve() != 0;
     262          19 :         psInfo->bReverseSolved = psInfo->poReverse->solve() != 0;
     263             :     }
     264             : 
     265          19 :     if (!psInfo->bForwardSolved || !psInfo->bReverseSolved)
     266             :     {
     267           2 :         GDALDestroyTPSTransformer(psInfo);
     268           2 :         return nullptr;
     269             :     }
     270             : 
     271          17 :     return psInfo;
     272             : }
     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          19 : void GDALDestroyTPSTransformer(void *pTransformArg)
     289             : 
     290             : {
     291          19 :     if (pTransformArg == nullptr)
     292           0 :         return;
     293             : 
     294          19 :     TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(pTransformArg);
     295             : 
     296          19 :     if (CPLAtomicDec(&(psInfo->nRefCount)) == 0)
     297             :     {
     298          19 :         delete psInfo->poForward;
     299          19 :         delete psInfo->poReverse;
     300             : 
     301          19 :         delete psInfo;
     302             :     }
     303             : }
     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.
     328             :  */
     329             : 
     330       34790 : int GDALTPSTransform(void *pTransformArg, int bDstToSrc, int nPointCount,
     331             :                      double *x, double *y, CPL_UNUSED double *z,
     332             :                      int *panSuccess)
     333             : {
     334       34790 :     VALIDATE_POINTER1(pTransformArg, "GDALTPSTransform", 0);
     335             : 
     336       34790 :     TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(pTransformArg);
     337             : 
     338      112088 :     for (int i = 0; i < nPointCount; i++)
     339             :     {
     340       77298 :         double xy_out[2] = {0.0, 0.0};
     341             : 
     342       77298 :         if (bDstToSrc)
     343             :         {
     344             :             // Compute initial guess
     345       70066 :             psInfo->poReverse->get_point(x[i], y[i], xy_out);
     346             : 
     347      461315 :             const auto ForwardTransformer = [](double xIn, double yIn,
     348             :                                                double &xOut, double &yOut,
     349             :                                                void *pUserData)
     350             :             {
     351      461315 :                 double xyOut[2] = {0.0, 0.0};
     352      461315 :                 TPSTransformInfo *l_psInfo =
     353             :                     static_cast<TPSTransformInfo *>(pUserData);
     354      461315 :                 l_psInfo->poForward->get_point(xIn, yIn, xyOut);
     355      461315 :                 xOut = xyOut[0];
     356      461315 :                 yOut = xyOut[1];
     357      461315 :                 return true;
     358             :             };
     359             : 
     360             :             // Refine the initial guess
     361       70066 :             GDALGenericInverse2D(
     362       70066 :                 x[i], y[i], xy_out[0], xy_out[1], ForwardTransformer, psInfo,
     363             :                 xy_out[0], xy_out[1],
     364             :                 /* computeJacobianMatrixOnlyAtFirstIter = */ true,
     365             :                 /* toleranceOnOutputCoordinates = */ 0,
     366             :                 psInfo->dfSrcApproxErrorReverse);
     367       70066 :             x[i] = xy_out[0];
     368       70066 :             y[i] = xy_out[1];
     369             :         }
     370             :         else
     371             :         {
     372        7232 :             psInfo->poForward->get_point(x[i], y[i], xy_out);
     373        7232 :             x[i] = xy_out[0];
     374        7232 :             y[i] = xy_out[1];
     375             :         }
     376       77298 :         panSuccess[i] = TRUE;
     377             :     }
     378             : 
     379       34790 :     return TRUE;
     380             : }
     381             : 
     382             : /************************************************************************/
     383             : /*                    GDALSerializeTPSTransformer()                     */
     384             : /************************************************************************/
     385             : 
     386           2 : CPLXMLNode *GDALSerializeTPSTransformer(void *pTransformArg)
     387             : 
     388             : {
     389           2 :     VALIDATE_POINTER1(pTransformArg, "GDALSerializeTPSTransformer", nullptr);
     390             : 
     391           2 :     TPSTransformInfo *psInfo = static_cast<TPSTransformInfo *>(pTransformArg);
     392             : 
     393             :     CPLXMLNode *psTree =
     394           2 :         CPLCreateXMLNode(nullptr, CXT_Element, "TPSTransformer");
     395             : 
     396             :     /* -------------------------------------------------------------------- */
     397             :     /*      Serialize bReversed.                                            */
     398             :     /* -------------------------------------------------------------------- */
     399           2 :     CPLCreateXMLElementAndValue(
     400             :         psTree, "Reversed",
     401           4 :         CPLString().Printf("%d", static_cast<int>(psInfo->bReversed)));
     402             : 
     403             :     /* -------------------------------------------------------------------- */
     404             :     /*      Attach GCP List.                                                */
     405             :     /* -------------------------------------------------------------------- */
     406           2 :     if (!psInfo->asGCPs.empty())
     407             :     {
     408           2 :         GDALSerializeGCPListToXML(psTree, psInfo->asGCPs, nullptr);
     409             :     }
     410             : 
     411           2 :     if (psInfo->dfSrcApproxErrorReverse > 0)
     412             :     {
     413           2 :         CPLCreateXMLElementAndValue(
     414             :             psTree, "SrcApproxErrorInPixel",
     415           4 :             CPLString().Printf("%g", psInfo->dfSrcApproxErrorReverse));
     416             :     }
     417             : 
     418           2 :     return psTree;
     419             : }
     420             : 
     421             : /************************************************************************/
     422             : /*                   GDALDeserializeTPSTransformer()                    */
     423             : /************************************************************************/
     424             : 
     425           3 : void *GDALDeserializeTPSTransformer(CPLXMLNode *psTree)
     426             : 
     427             : {
     428             :     /* -------------------------------------------------------------------- */
     429             :     /*      Check for GCPs.                                                 */
     430             :     /* -------------------------------------------------------------------- */
     431           3 :     CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList");
     432             : 
     433           6 :     std::vector<gdal::GCP> asGCPs;
     434           3 :     if (psGCPList != nullptr)
     435             :     {
     436           3 :         GDALDeserializeGCPListFromXML(psGCPList, asGCPs, nullptr);
     437             :     }
     438             : 
     439             :     /* -------------------------------------------------------------------- */
     440             :     /*      Get other flags.                                                */
     441             :     /* -------------------------------------------------------------------- */
     442           3 :     const int bReversed = atoi(CPLGetXMLValue(psTree, "Reversed", "0"));
     443             : 
     444           3 :     CPLStringList aosOptions;
     445             :     aosOptions.SetNameValue(
     446             :         "SRC_APPROX_ERROR_IN_PIXEL",
     447           3 :         CPLGetXMLValue(psTree, "SrcApproxErrorInPixel", nullptr));
     448             : 
     449             :     /* -------------------------------------------------------------------- */
     450             :     /*      Generate transformation.                                        */
     451             :     /* -------------------------------------------------------------------- */
     452           3 :     void *pResult = GDALCreateTPSTransformerInt(static_cast<int>(asGCPs.size()),
     453             :                                                 gdal::GCP::c_ptr(asGCPs),
     454             :                                                 bReversed, aosOptions.List());
     455             : 
     456           6 :     return pResult;
     457             : }

Generated by: LCOV version 1.14