├── .gitignore ├── README.md ├── examples ├── CMakeLists.txt ├── cmake │ └── FindEigen3.cmake └── tps_cube_deformation.cpp └── src └── tps └── tps.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This repository has been archived.** 2 | 3 | thin-plate-splines 4 | ================== 5 | 6 | A C++ implementation of polyharmonic/thin-plate spline transformation/interpolation. 7 | 8 | A deformation of the unit cube using thin-plate splines: 9 | ![Deformation of the unit cube using thin-plate splines](https://raw.github.com/vladimir-ch/vladimir-ch.github.io/master/images/tps_sample.png) 10 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2011-2013 Vladimir Chalupecky 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to 6 | # deal in the Software without restriction, including without limitation the 7 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | # sell copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | # IN THE SOFTWARE. 21 | 22 | cmake_minimum_required(VERSION 2.8.0) 23 | 24 | project(tps_examples CXX) 25 | 26 | if (NOT CMAKE_BUILD_TYPE) 27 | set(CMAKE_BUILD_TYPE "Release") 28 | endif() 29 | 30 | set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 31 | 32 | find_package(Eigen3 REQUIRED) 33 | find_package(Boost REQUIRED) 34 | 35 | include_directories( 36 | ${EIGEN3_INCLUDE_DIR} 37 | ${Boost_INCLUDE_DIR} 38 | ${PROJECT_SOURCE_DIR}/../src 39 | ) 40 | 41 | add_executable(tps_cube_deformation tps_cube_deformation.cpp) 42 | -------------------------------------------------------------------------------- /examples/cmake/FindEigen3.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Eigen3 lib 2 | # 3 | # This module supports requiring a minimum version, e.g. you can do 4 | # find_package(Eigen3 3.1.2) 5 | # to require version 3.1.2 or newer of Eigen3. 6 | # 7 | # Once done this will define 8 | # 9 | # EIGEN3_FOUND - system has eigen lib with correct version 10 | # EIGEN3_INCLUDE_DIR - the eigen include directory 11 | # EIGEN3_VERSION - eigen version 12 | 13 | # Copyright (c) 2006, 2007 Montel Laurent, 14 | # Copyright (c) 2008, 2009 Gael Guennebaud, 15 | # Copyright (c) 2009 Benoit Jacob 16 | # Redistribution and use is allowed according to the terms of the 2-clause BSD license. 17 | 18 | if(NOT Eigen3_FIND_VERSION) 19 | if(NOT Eigen3_FIND_VERSION_MAJOR) 20 | set(Eigen3_FIND_VERSION_MAJOR 2) 21 | endif(NOT Eigen3_FIND_VERSION_MAJOR) 22 | if(NOT Eigen3_FIND_VERSION_MINOR) 23 | set(Eigen3_FIND_VERSION_MINOR 91) 24 | endif(NOT Eigen3_FIND_VERSION_MINOR) 25 | if(NOT Eigen3_FIND_VERSION_PATCH) 26 | set(Eigen3_FIND_VERSION_PATCH 0) 27 | endif(NOT Eigen3_FIND_VERSION_PATCH) 28 | 29 | set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") 30 | endif(NOT Eigen3_FIND_VERSION) 31 | 32 | macro(_eigen3_check_version) 33 | file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) 34 | 35 | string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") 36 | set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") 37 | string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") 38 | set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") 39 | string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") 40 | set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") 41 | 42 | set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) 43 | if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 44 | set(EIGEN3_VERSION_OK FALSE) 45 | else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 46 | set(EIGEN3_VERSION_OK TRUE) 47 | endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) 48 | 49 | if(NOT EIGEN3_VERSION_OK) 50 | 51 | message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " 52 | "but at least version ${Eigen3_FIND_VERSION} is required") 53 | endif(NOT EIGEN3_VERSION_OK) 54 | endmacro(_eigen3_check_version) 55 | 56 | if (EIGEN3_INCLUDE_DIR) 57 | 58 | # in cache already 59 | _eigen3_check_version() 60 | set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) 61 | 62 | else (EIGEN3_INCLUDE_DIR) 63 | 64 | find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library 65 | PATHS 66 | ${CMAKE_INSTALL_PREFIX}/include 67 | ${KDE4_INCLUDE_DIR} 68 | PATH_SUFFIXES eigen3 eigen 69 | ) 70 | 71 | if(EIGEN3_INCLUDE_DIR) 72 | _eigen3_check_version() 73 | endif(EIGEN3_INCLUDE_DIR) 74 | 75 | include(FindPackageHandleStandardArgs) 76 | find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) 77 | 78 | mark_as_advanced(EIGEN3_INCLUDE_DIR) 79 | 80 | endif(EIGEN3_INCLUDE_DIR) 81 | 82 | -------------------------------------------------------------------------------- /examples/tps_cube_deformation.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2013 Vladimir Chalupecky 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to 6 | // deal in the Software without restriction, including without limitation the 7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | // sell copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | // IN THE SOFTWARE. 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | typedef tps::Thin_plate_spline_transformation< 3, 3 > Transformation; 32 | typedef Transformation::Domain_point Domain_point; 33 | typedef Transformation::Range_point Range_point; 34 | 35 | int main() 36 | { 37 | std::vector< Domain_point > dp; 38 | dp.push_back(Domain_point(0, 0, 0)); 39 | dp.push_back(Domain_point(1, 0, 0)); 40 | dp.push_back(Domain_point(0, 1, 0)); 41 | dp.push_back(Domain_point(1, 1, 0)); 42 | dp.push_back(Domain_point(0, 0, 1)); 43 | dp.push_back(Domain_point(1, 0, 1)); 44 | dp.push_back(Domain_point(0, 1, 1)); 45 | dp.push_back(Domain_point(1, 1, 1)); 46 | 47 | boost::variate_generator > 48 | gen(boost::mt19937(time(0)), boost::normal_distribution<>(0.0, 0.2)); 49 | 50 | std::vector< Range_point > rp; 51 | rp.push_back(Range_point(0 + gen(), 0 + gen(), 0 + gen())); 52 | rp.push_back(Range_point(1 + gen(), 0 + gen(), 0 + gen())); 53 | rp.push_back(Range_point(0 + gen(), 1 + gen(), 0 + gen())); 54 | rp.push_back(Range_point(1 + gen(), 1 + gen(), 0 + gen())); 55 | rp.push_back(Range_point(0 + gen(), 0 + gen(), 1 + gen())); 56 | rp.push_back(Range_point(1 + gen(), 0 + gen(), 1 + gen())); 57 | rp.push_back(Range_point(0 + gen(), 1 + gen(), 1 + gen())); 58 | rp.push_back(Range_point(1 + gen(), 1 + gen(), 1 + gen())); 59 | 60 | Transformation tps(dp.begin(), dp.end(), rp.begin(), rp.end()); 61 | std::cout << "Integral bending norm: " << tps.integral_bending_norm() << std::endl; 62 | 63 | std::ofstream tpsfile("tps_test.vtk"); 64 | tpsfile << "# vtk DataFile Version 2.0\n"; 65 | tpsfile << "tps\n"; 66 | tpsfile << "ASCII\nDATASET POLYDATA\n"; 67 | tpsfile << "POINTS " << 11 * 11 * 11 << " float\n"; 68 | 69 | for (std::size_t i = 0; i < 11; ++i) 70 | { 71 | for (std::size_t j = 0; j < 11; ++j) 72 | { 73 | for (std::size_t k = 0; k < 11; ++k) 74 | { 75 | Domain_point p(i * 0.1, j * 0.1, k * 0.1); 76 | Range_point r = tps.transform(p); 77 | tpsfile << r(0) << " " << r(1) << " " << r(2) << std::endl; 78 | } 79 | } 80 | } 81 | 82 | return 0; 83 | } 84 | -------------------------------------------------------------------------------- /src/tps/tps.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2013 Vladimir Chalupecky 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to 6 | // deal in the Software without restriction, including without limitation the 7 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | // sell copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | // IN THE SOFTWARE. 21 | 22 | #ifndef TPS_TPS_HPP 23 | #define TPS_TPS_HPP 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | namespace tps { 38 | namespace detail { 39 | 40 | template< int k > 41 | struct Polyharmonic_spline_kernel_odd 42 | { 43 | double operator()(double r) 44 | { 45 | return std::pow(r, k); 46 | } 47 | }; 48 | 49 | template< int k > 50 | struct Polyharmonic_spline_kernel_even 51 | { 52 | double operator()(double r) 53 | { 54 | if (r < 1.0) 55 | { 56 | return std::pow(r, k - 1) * std::log(std::pow(r, r)); 57 | } 58 | 59 | return std::pow(r, k) * std::log(r); 60 | } 61 | }; 62 | 63 | } // namespace detail 64 | 65 | template< int k > 66 | struct Polyharmonic_spline_kernel : public boost::conditional < k % 2, 67 | detail::Polyharmonic_spline_kernel_odd, 68 | detail::Polyharmonic_spline_kernel_even >::type 69 | {}; 70 | 71 | 72 | template< int Domain_dim, int Range_dim, typename Kernel_function > 73 | class Polyharmonic_spline_transformation 74 | { 75 | public: 76 | typedef Eigen::Matrix< double, Domain_dim, 1 > Domain_point; 77 | typedef Eigen::Matrix< double, Range_dim, 1 > Range_point; 78 | 79 | private: 80 | typedef std::vector< Domain_point > Domain_points_container; 81 | typedef std::vector< Range_point> Range_points_container; 82 | typedef Eigen::MatrixXd Matrix; 83 | 84 | public: 85 | template< typename Forward_iterator_1, typename Forward_iterator_2 > 86 | Polyharmonic_spline_transformation( 87 | Forward_iterator_1 domain_points_begin, 88 | Forward_iterator_1 domain_points_end, 89 | Forward_iterator_2 range_points_begin, 90 | Forward_iterator_2 range_points_end, 91 | double max_relative_error = 1.0e-8 92 | ) 93 | : domain_points_(domain_points_begin, domain_points_end) 94 | , bending_norm_(0.0) 95 | , max_relative_error_(max_relative_error) 96 | { 97 | if (domain_points_.size() < Domain_dim + 1) 98 | { 99 | throw 100 | std::runtime_error( 101 | "Polyharmonic_spline_transformation(): " 102 | "Insufficient number of corresponding pairs given" 103 | ); 104 | } 105 | 106 | assemble_L_matrix(); 107 | set_range_points(range_points_begin, range_points_end); 108 | } 109 | 110 | Range_point transform(Domain_point p) 111 | { 112 | std::size_t N = domain_points_.size(); 113 | std::size_t M = N + 1 + Domain_dim; 114 | 115 | Range_point result = Wa_.block<1, Range_dim>(N, 0).transpose(); 116 | 117 | for (std::size_t i = 0; i < Domain_dim; ++i) 118 | { 119 | result += p(i) * Wa_.block<1, Range_dim>(N + 1 + i, 0).transpose(); 120 | } 121 | 122 | for (std::size_t i = 0; i < N; ++i) 123 | { 124 | if (p != domain_points_[i]) 125 | { 126 | result += kernel_((domain_points_[i] - p).norm()) 127 | * Wa_.block<1, Range_dim>(i, 0).transpose(); 128 | } 129 | } 130 | 131 | return result; 132 | } 133 | 134 | template < typename Forward_iterator > 135 | void set_range_points(Forward_iterator begin, Forward_iterator end) 136 | { 137 | if (domain_points_.size() != std::distance(begin, end)) 138 | { 139 | throw 140 | std::runtime_error( 141 | "Polyharmonic_spline_transformation::set_range_points(): " 142 | "The number of domain and range points must be the " 143 | "same." 144 | ); 145 | } 146 | 147 | assemble_Vt_matrix(begin, end); 148 | update_Wa_matrix(); 149 | } 150 | 151 | double integral_bending_norm() const 152 | { 153 | return bending_norm_; 154 | } 155 | 156 | private: 157 | template < typename Forward_iterator > 158 | void assemble_Vt_matrix(Forward_iterator begin, Forward_iterator end) 159 | { 160 | std::size_t N = domain_points_.size(); 161 | std::size_t M = N + 1 + Domain_dim; 162 | 163 | Vt_.resize(M, Range_dim); 164 | std::size_t i = 0; 165 | 166 | for (Forward_iterator it = begin; it != end; ++it, ++i) 167 | { 168 | Vt_.block<1, Range_dim>(i, 0) = *it; 169 | } 170 | 171 | Vt_.bottomLeftCorner < Domain_dim + 1, Range_dim > ().setZero(); 172 | } 173 | 174 | 175 | void assemble_L_matrix() 176 | { 177 | std::size_t N = domain_points_.size(); 178 | std::size_t M = N + 1 + Domain_dim; 179 | 180 | L_.resize(M, M); 181 | 182 | for (std::size_t i = 0; i < N; ++i) 183 | { 184 | L_(i, i) = 0.0; 185 | 186 | for (std::size_t j = i + 1; j < N; ++j) 187 | { 188 | if (domain_points_[i] == domain_points_[j]) 189 | { 190 | throw std::runtime_error( 191 | "Polyharmonic_spline_transformation::assemble_L_matrix(): " 192 | "Degenerate input points"); 193 | } 194 | 195 | double d = (domain_points_[i] - domain_points_[j]).norm(); 196 | 197 | if (d < minimum_distance_) 198 | { 199 | std::cerr << 200 | "Warning: Polyharmonic_spline_transformation::assemble_L_matrix(): " 201 | "Input points " << i << " and " << j << 202 | " are too close (dist(i,j) < " << minimum_distance_ 203 | << ')' << std::endl; 204 | } 205 | 206 | L_(i, j) = L_(j, i) = kernel_(d); 207 | } 208 | } 209 | 210 | for (std::size_t i = 0; i < N; ++i) 211 | { 212 | L_.block<1, Domain_dim>(i, N + 1) = domain_points_[i].transpose(); 213 | L_.block(N + 1, i) = domain_points_[i]; 214 | } 215 | 216 | L_.col(N).setOnes(); 217 | L_.row(N).setOnes(); 218 | L_.bottomRightCorner < Domain_dim + 1, Domain_dim + 1 > ().setZero(); 219 | } 220 | 221 | void update_Wa_matrix() 222 | { 223 | std::size_t N = domain_points_.size(); 224 | std::size_t M = N + 1 + Domain_dim; 225 | 226 | Wa_.resize(M, Range_dim); 227 | Wa_ = L_.fullPivLu().solve(Vt_); 228 | double relative_error = (L_ * Wa_ - Vt_).norm() / Vt_.norm(); 229 | 230 | if (std::isnan(relative_error)) 231 | { 232 | throw 233 | std::runtime_error( 234 | "Polyharmonic_spline_transformation::update_Wa_matrix(): " 235 | "Cannot define transformation (probably degenerate input " 236 | "points)" 237 | ); 238 | } 239 | 240 | if (relative_error > max_relative_error_) 241 | { 242 | throw 243 | std::runtime_error( 244 | "Polyharmonic_spline_transformation::update_Wa_matrix(): " 245 | "Relative error too large" 246 | ); 247 | } 248 | 249 | Matrix bending_norm = Matrix::Zero(1, 1); 250 | 251 | for (std::size_t i = 0; i < Range_dim; ++i) 252 | { 253 | bending_norm += Wa_.block(0, i, N, 1).transpose() 254 | * L_.block(0, 0, N, N) 255 | * Wa_.block(0, i, N, 1); 256 | } 257 | 258 | bending_norm_ = bending_norm(0, 0); 259 | } 260 | 261 | Domain_points_container domain_points_; 262 | Kernel_function kernel_; 263 | Matrix L_, Vt_, Wa_; 264 | double bending_norm_, max_relative_error_; 265 | 266 | static double const minimum_distance_ = 1.0e-6; 267 | }; 268 | 269 | template < int Domain_dim, int Range_dim > 270 | class Thin_plate_spline_transformation : public Polyharmonic_spline_transformation< 271 | Domain_dim, 272 | Range_dim, 273 | Polyharmonic_spline_kernel< 2 > > 274 | { 275 | typedef Polyharmonic_spline_transformation< 276 | Domain_dim, 277 | Range_dim, 278 | Polyharmonic_spline_kernel< 2 > > Base; 279 | 280 | public: 281 | template< typename Forward_iterator_1, typename Forward_iterator_2 > 282 | Thin_plate_spline_transformation( 283 | Forward_iterator_1 domain_points_begin, 284 | Forward_iterator_1 domain_points_end, 285 | Forward_iterator_2 range_points_begin, 286 | Forward_iterator_2 range_points_end, 287 | double max_relative_error = 1.0e-8 288 | ) 289 | : Base(domain_points_begin, domain_points_end, range_points_begin, 290 | range_points_end, max_relative_error) 291 | {} 292 | }; 293 | 294 | } // namespace tps 295 | 296 | #endif // TPS_TPS_HPP 297 | --------------------------------------------------------------------------------