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

Generated by: LCOV version 1.14