diff --git a/.gitignore b/.gitignore index 2b1cc7ce60..696da95405 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ install_manifest.txt # # visual studio cruft # +/.vs* Debug/ Release/ RelWithDebInfo/ diff --git a/cmake/macros.cmake b/cmake/macros.cmake index ef3ed9d178..0530cde6a7 100644 --- a/cmake/macros.cmake +++ b/cmake/macros.cmake @@ -54,6 +54,7 @@ macro(PDAL_ADD_LIBRARY _name) set_property(TARGET ${_name} PROPERTY FOLDER "Libraries") target_include_directories(${_name} PRIVATE ${PDAL_INCLUDE_DIR}) + PDAL_TARGET_COMPILE_SETTINGS(${_name}) install(TARGETS ${_name} EXPORT PDALTargets @@ -73,6 +74,7 @@ macro(PDAL_ADD_FREE_LIBRARY _name _library_type) set_property(TARGET ${_name} PROPERTY FOLDER "Libraries") target_include_directories(${_name} PRIVATE ${PDAL_INCLUDE_DIR}) + PDAL_TARGET_COMPILE_SETTINGS(${_name}) install(TARGETS ${_name} EXPORT PDALTargets diff --git a/cmake/options.cmake b/cmake/options.cmake index c30f4bf17d..add10850a9 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -8,9 +8,9 @@ add_feature_info("Bash completion" WITH_COMPLETION "completion for PDAL command line") option(BUILD_PLUGIN_CPD - "Choose if Coherent Point Drift kernel is built" FALSE) + "Choose if the cpd filter should be built" FALSE) add_feature_info("CPD plugin" BUILD_PLUGIN_CPD - "run Coherent Point Drift on two datasets") + "Coherent Point Drift (CPD) computes rigid or nonrigid transformations between point sets") option(BUILD_PLUGIN_GEOWAVE "Choose if GeoWave support should be built" FALSE) diff --git a/cmake/unix_compiler_options.cmake b/cmake/unix_compiler_options.cmake index 5a77477439..4875d592bc 100644 --- a/cmake/unix_compiler_options.cmake +++ b/cmake/unix_compiler_options.cmake @@ -1,22 +1,31 @@ -set(PDAL_COMMON_CXX_FLAGS "-Wextra -Wall -Wno-unused-parameter -Wno-unused-variable -Wpointer-arith -Wcast-align -Wcast-qual -Wredundant-decls -Wno-long-long -Wno-unknown-pragmas -Wno-deprecated-declarations -isystem /usr/local/include" -) - if (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") if (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4.7) - set(CXX_STANDARD "-std=c++0x") + set(PDAL_CXX_STANDARD "-std=c++0x") else() - set(CXX_STANDARD "-std=c++11") - endif() - if (${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER 4.6) - set(PDAL_NO_AS_NEEDED_START "-Wl,--no-as-needed") - set(PDAL_NO_AS_NEEDED_END "-Wl,--as-needed") + set(PDAL_CXX_STANDARD "-std=c++11") endif() set(PDAL_COMPILER_GCC 1) elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") - set(CXX_STANDARD "-std=c++11") + set(PDAL_CXX_STANDARD "-std=c++11") set(PDAL_COMPILER_CLANG 1) else() message(FATAL_ERROR "Unsupported C++ compiler") endif() -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PDAL_COMMON_CXX_FLAGS} ${CXX_STANDARD}") +function(PDAL_TARGET_COMPILE_SETTINGS target) + target_compile_options(${target} PUBLIC + ${PDAL_CXX_STANDARD} + -Wextra + -Wall + -Wno-unused-parameter + -Wno-unused-variable + -Wpointer-arith + -Wcast-align + -Wcast-qual + -Wredundant-decls + -Wno-long-long + -Wno-unknown-pragmas + -Wno-deprecated-declarations + ) + target_include_directories(${target} SYSTEM PUBLIC /usr/local/include) +endfunction() diff --git a/cmake/win32_compiler_options.cmake b/cmake/win32_compiler_options.cmake index 5ccf9e6b24..c702dd254c 100644 --- a/cmake/win32_compiler_options.cmake +++ b/cmake/win32_compiler_options.cmake @@ -11,77 +11,77 @@ if (MSVC) elseif (MSVC8) set(PDAL_COMPILER_VC8 1) endif() +endif() - add_definitions(-DBOOST_ALL_NO_LIB) +function(PDAL_TARGET_COMPILE_SETTINGS target) + target_compile_definitions(${target} PUBLIC -DWIN32_LEAN_AND_MEAN) + if (MSVC) + target_compile_definitions(${target} PUBLIC -DBOOST_ALL_NO_LIB) - # check for MSVC 8+ - if (NOT (MSVC_VERSION VERSION_LESS 1400)) - add_definitions(-D_CRT_SECURE_NO_DEPRECATE) - add_definitions(-D_CRT_SECURE_NO_WARNINGS) - add_definitions(-D_CRT_NONSTDC_NO_WARNING) - add_definitions(-D_SCL_SECURE_NO_WARNINGS) - add_definitions(-DNOMINMAX) + # check for MSVC 8+ + if (NOT (MSVC_VERSION VERSION_LESS 1400)) + target_compile_definitions(${target} PUBLIC + -D_CRT_SECURE_NO_DEPRECATE + -D_CRT_SECURE_NO_WARNINGS + -D_CRT_NONSTDC_NO_WARNING + -D_SCL_SECURE_NO_WARNINGS + -DNOMINMAX + ) + target_compile_options(${target} PUBLIC + # Nitro makes use of Exception Specifications, which results in + # numerous warnings when compiling in MSVC. We will ignore them for + # now. + /wd4290 + /wd4800 + # Windows warns about integer narrowing like crazy and it's annoying. + # In most cases the programmer knows what they're doing. A good + # static analysis tool would be better than turning this warning off. + /wd4267 + ) + endif() + + # check for MSVC 9+ + if (NOT (MSVC_VERSION VERSION_LESS 1500)) + include(ProcessorCount) + ProcessorCount(N) + if(NOT N EQUAL 0) + target_compile_options(${target} PRIVATE "/MP${N}") + endif() + endif() - # Nitro makes use of Exception Specifications, which results in - # numerous warnings when compiling in MSVC. We will ignore them for - # now. - add_definitions("/wd4290") - add_definitions("/wd4800") + option(PDAL_USE_STATIC_RUNTIME "Use the static runtime" FALSE) + if (PDAL_USE_STATIC_RUNTIME) + target_compile_options(${target} PRIVATE /MT) + endif() - # Windows warns about integer narrowing like crazy and it's annoying. - # In most cases the programmer knows what they're doing. A good - # static analysis tool would be better than turning this warning off. - add_definitions("/wd4267") endif() +endfunction() +if (MSVC) if (CMAKE_CXX_FLAGS MATCHES "/W[0-4]") string(REGEX REPLACE "/W[0-4]" "/W3" - CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3") endif() - # check for MSVC 9+ - if (NOT (MSVC_VERSION VERSION_LESS 1500)) - include(ProcessorCount) - ProcessorCount(N) - if(NOT N EQUAL 0) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP${N}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP${N}") - endif() - endif() - - option(PDAL_USE_STATIC_RUNTIME "Use the static runtime" FALSE) if (PDAL_USE_STATIC_RUNTIME) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT") - - # Note that the CMake cache will still show /MD - # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace - foreach(flag_var - CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE - CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) - if(${flag_var} MATCHES "/MD") - string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") - endif(${flag_var} MATCHES "/MD") - endforeach(flag_var) + # Note that the CMake cache will still show /MD + # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace + foreach(flag_var + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if(${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif(${flag_var} MATCHES "/MD") + endforeach(flag_var) endif() - -endif(MSVC) -add_definitions(-DWIN32_LEAN_AND_MEAN) - -# note we default to debug mode -#if(NOT MSVC_IDE) -# if(NOT CMAKE_BUILD_TYPE) -# set(CMAKE_BUILD_TYPE Debug CACHE STRING -# "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel" FORCE) -# endif() -# message(STATUS "Setting PDAL build type - ${CMAKE_BUILD_TYPE}") -#endif() +endif() set(CMAKE_INCLUDE_PATH "c:/OSGeo4W64/include;$ENV{CMAKE_INCLUDE_PATH}") set(CMAKE_LIBRARY_PATH "c:/OSGeo4W64/lib;$ENV{CMAKE_LIBRARY_PATH}") set(CMAKE_PREFIX_PATH "c:/OSGeo4W64/cmake;$ENV{CMAKE_LIBRARY_PATH}") -#ABELL - WHY? +#ABELL (& gadomski) - WHY? set(PDAL_PLATFORM_WIN32 1) set(WINSOCK_LIBRARY ws2_32) diff --git a/dimbuilder/CMakeLists.txt b/dimbuilder/CMakeLists.txt index d9791cb436..6f232e2750 100644 --- a/dimbuilder/CMakeLists.txt +++ b/dimbuilder/CMakeLists.txt @@ -26,6 +26,7 @@ add_executable(dimbuilder target_include_directories(dimbuilder PRIVATE ${PDAL_INCLUDE_DIR} ${PDAL_JSONCPP_INCLUDE_DIR}) +PDAL_TARGET_COMPILE_SETTINGS(dimbuilder) if (PDAL_HAVE_JSONCPP) target_link_libraries(dimbuilder PRIVATE ${PDAL_JSONCPP_LIB_NAME}) endif() diff --git a/doc/development/goals.rst b/doc/development/goals.rst index 4718537cf4..2455877e51 100644 --- a/doc/development/goals.rst +++ b/doc/development/goals.rst @@ -9,7 +9,7 @@ Project Goals command line tools are provided. As `GDAL`_ is to 2D pixels, `OGR`_ is to geospatial vector geometry, PDAL is to multidimensional points. -2. From a market perspective, PDAL is "version 2" of libLAS. The actual +2. From a market perspective, PDAL is "version 2" of `libLAS`_. The actual code base will be different, however, and the APIs will not be compatible. @@ -30,3 +30,4 @@ Project Goals .. _`GDAL`: http://gdal.org .. _`OGR`: http://gdal.org/ogr/ +.. _`libLAS`: https://www.liblas.org diff --git a/doc/stages/filters.cpd.rst b/doc/stages/filters.cpd.rst new file mode 100644 index 0000000000..1347c81f47 --- /dev/null +++ b/doc/stages/filters.cpd.rst @@ -0,0 +1,73 @@ +.. _filters.cpd: + +filters.cpd +============== + +The CPD filter uses the Coherent Point Drift :cite:`Myronenko` algorithm to compute a rigid, nonrigid, or affine transformation between datasets. +The rigid and affine are what you'd expect; the nonrigid transformation uses Motion Coherence Theory :cite:`Yuille1998` to "bend" the points to find a best alignment. + +.. note:: + + CPD is computationally intensive and can be slow when working with many points (i.e. > 10 000). + Nonrigid is significatly slower than rigid and affine. + +The first input to the change filter are considered the "fixed" points, and all subsequent inputs are "moving" points. +The output from the change filter are the "moving" points after the calculated transformation has been applied, one point view per input. +Any additional information about the cpd registration, e.g. the rigid transformation matrix, will be placed in the stage's metadata. + +Examples +-------- + +.. code-block:: json + + { + "pipeline": [ + "fixed.las", + "moving.las", + { + "type": "filters.rigid", + "method": "rigid" + }, + "output.las" + ] + } + +If "method" is not provided, the cpd filter will default to using the rigid registration method. +To get the transform matrix, you'll need to use the ``--metadata`` option: + +:: + + $ pdal pipeline cpd-pipeline.json --metadata cpd-metadata.json + +The metadata output might start something like: + +.. code-block:: json + + { + "stages": + { + "filters.cpd": + { + "iterations": 10, + "method": "rigid", + "runtime": 0.003839, + "sigma2": 5.684342128e-16, + "transform": " 1 -6.21722e-17 1.30104e-18 5.29303e-11-8.99346e-17 1 2.60209e-18 -3.49247e-10 -2.1684e-19 1.73472e-18 1 -1.53477e-12 0 0 0 1" + }, + }, + +.. seealso:: + + :ref:`filters.transformation` to apply a transform to other points. + +Options +-------- + +method + Change detection method to use. + Valid values are "rigid", "affine", and "nonrigid". + [Default: **rigid**] + +.. _Coherent Point Drift (CPD): https://github.com/gadomski/cpd + +.. bibliography:: references.bib diff --git a/doc/stages/filters.head.rst b/doc/stages/filters.head.rst new file mode 100644 index 0000000000..f6433fb457 --- /dev/null +++ b/doc/stages/filters.head.rst @@ -0,0 +1,75 @@ +.. _filters.head: + +filters.head +=============================================================================== + +The HeadFilter returns a specified number of points from the beginning of the +PointView. + +.. note:: + + If the requested number of points exceeds the size of the point cloud, all + points are passed with a warning. + + +Example #1 +---------- + +Thin a point cloud by first shuffling the point order with +:ref:`filters.randomize` and then picking the first 10000 using the HeadFilter. + + +.. code-block:: json + + { + "pipeline":[ + { + "type":"filters.randomize" + }, + { + "type":"filters.head", + "count":10000 + } + ] + } + + +Example #2 +---------- + +Compute height above ground and extract the ten highest points. + + +.. code-block:: json + + { + "pipeline":[ + { + "type":"filters.smrf" + }, + { + "type":"filters.hag" + }, + { + "type":"filters.sort", + "dimension":"HeightAboveGround", + "order":"DESC" + }, + { + "type":"filters.head", + "count":10 + } + ] + } + + +.. seealso:: + + :ref:`filters.tail` is the dual to :ref:`filters.head`. + + +Options +------------------------------------------------------------------------------- + +count + Number of points to return. [Default: **10**] diff --git a/doc/stages/filters.icp.rst b/doc/stages/filters.icp.rst new file mode 100644 index 0000000000..f6a1716747 --- /dev/null +++ b/doc/stages/filters.icp.rst @@ -0,0 +1,56 @@ +.. _filters.icp: + +filters.icp +============== + +The ICP filter uses the `PCL's Iterative Closest Point (ICP)`_ algorithm to calculate a rigid (rotation and translation) transformation that best aligns two datasets. +The first input to the ICP filter is considered the "fixed" points, and all subsequent points are "moving" points. +The output from the change filter are the "moving" points after the calculated transformation has been applied, one point view per input. +The transformation matrix is inserted into the stage's metadata. + +Examples +-------- + +.. code-block:: json + + { + "pipeline": [ + "fixed.las", + "moving.las", + { + "type": "filters.icp" + }, + "output.las" + ] + } + +To get the transform matrix, you'll need to use the ``--metadata`` option: + +:: + + $ pdal pipeline icp-pipeline.json --metadata icp-metadata.json + +The metadata output might start something like: + +.. code-block:: json + + { + "stages": + { + "filters.icp": + { + "converged": true, + "fitness": 0.01953125097, + "transform": " 1 2.60209e-18 -1.97906e-09 -0.375 8.9407e-08 1 5.58794e-09 -0.5625 6.98492e -10 -5.58794e-09 1 0.00411987 0 0 0 1" + } + +.. seealso:: + + :ref:`filters.transformation` to apply a transform to other points. + +Options +-------- + +None. + +.. _PCL's Iterative Closest Point (ICP): http://docs.pointclouds.org/trunk/classpcl_1_1_iterative_closest_point.html diff --git a/doc/stages/filters.tail.rst b/doc/stages/filters.tail.rst new file mode 100644 index 0000000000..39ae73d53c --- /dev/null +++ b/doc/stages/filters.tail.rst @@ -0,0 +1,47 @@ +.. _filters.tail: + +filters.tail +=============================================================================== + +The TailFilter returns a specified number of points from the end of the +PointView. + +.. note:: + + If the requested number of points exceeds the size of the point cloud, all + points are passed with a warning. + + +Example #1 +---------- + +Sort and extract the 100 lowest intensity points. + + +.. code-block:: json + + { + "pipeline":[ + { + "type":"filters.sort", + "dimension":"Intensity", + "order":"DESC" + }, + { + "type":"filters.tail", + "count":100 + } + ] + } + + +.. seealso:: + + :ref:`filters.head` is the dual to :ref:`filters.tail`. + + +Options +------------------------------------------------------------------------------- + +count + Number of points to return. [Default: **10**] diff --git a/doc/stages/filters.voxelcenternearestneighbor.rst b/doc/stages/filters.voxelcenternearestneighbor.rst new file mode 100644 index 0000000000..c33751be5f --- /dev/null +++ b/doc/stages/filters.voxelcenternearestneighbor.rst @@ -0,0 +1,49 @@ +.. _filters.voxelcenternearestneighbor: + +=============================================================================== +filters.voxelcenternearestneighbor +=============================================================================== + +VoxelCenterNearestNeighbor is a voxel-based sampling filter. The input point +cloud is divided into 3D voxels at the given cell size. For each populated +voxel, the coordinates of the voxel center are used as the query point in a 3D +nearest neighbor search. The nearest neighbor is then added to the output point +cloud, along with any existing dimensions. + +.. note:: + + This is similar to the existing :ref:`filters.voxelgrid`. However, in the + case of the VoxelGrid, the centroid of the points falling within the voxel + is added to the output point cloud. The drawback with this approach is that + all dimensional data is lost, and the sampled cloud now consists of only XYZ + coordinates. + +Example +------- + + +.. code-block:: json + + { + "pipeline":[ + "input.las", + { + "type":"filters.voxelcenternearestneighbor", + "cell":10.0 + }, + "output.las" + ] + } + + +.. seealso:: + + :ref:`filters.voxelcentroidnearestneighbor` offers a similar solution, using + as the query point the centroid of all points falling within the voxel as + opposed to the voxel center coordinates. + +Options +------------------------------------------------------------------------------- + +cell + Cell size in the X, Y, and Z dimension. [Default: **1.0**] diff --git a/doc/stages/filters.voxelcentroidnearestneighbor.rst b/doc/stages/filters.voxelcentroidnearestneighbor.rst new file mode 100644 index 0000000000..21c4c906c7 --- /dev/null +++ b/doc/stages/filters.voxelcentroidnearestneighbor.rst @@ -0,0 +1,47 @@ +.. _filters.voxelcentroidnearestneighbor: + +=============================================================================== +filters.voxelcentroidnearestneighbor +=============================================================================== + +VoxelCentroidNearestNeighbor is a voxel-based sampling filter. The input point +cloud is divided into 3D voxels at the given cell size. For each populated +voxel, the centroid of the points within that voxel is computed. This centroid +is used as the query point in a 3D nearest neighbor search. The nearest neighbor +is then added to the output point cloud, along with any existing dimensions. + +.. note:: + + This is similar to the existing :ref:`filters.voxelgrid`. However, in the + case of the VoxelGrid, the centroid itself is added to the output point + cloud. The drawback with this approach is that all dimensional data is lost, + and the sampled cloud now consists of only XYZ coordinates. + +Example +------- + + +.. code-block:: json + + { + "pipeline":[ + "input.las", + { + "type":"filters.voxelcentroidnearestneighbor", + "cell":10.0 + }, + "output.las" + ] + } + + +.. seealso:: + + :ref:`filters.voxelcenternearestneighbor` offers a similar solution, using + the voxel center as opposed to the voxel centroid for the query point. + +Options +------------------------------------------------------------------------------- + +cell + Cell size in the X, Y, and Z dimension. [Default: **1.0**] diff --git a/doc/stages/readers.faux.rst b/doc/stages/readers.faux.rst index 1c4dcc6290..08280f42bb 100644 --- a/doc/stages/readers.faux.rst +++ b/doc/stages/readers.faux.rst @@ -36,7 +36,7 @@ Example { "type":"readers.faux", "bounds":"([0,1000000],[0,1000000],[0,100])", - "num_points":"10000", + "count":"10000", "mode":"random" }, { @@ -54,7 +54,7 @@ bounds What spatial extent should points be generated within? Text string of the form "([xmin,xmax],[ymin,ymax],[zmin,zmax])". [Default: unit cube] -num_points +count How many synthetic points to generate before finishing? [Required] mean_x|y|z diff --git a/doc/stages/references.bib b/doc/stages/references.bib new file mode 100644 index 0000000000..726e5ae485 --- /dev/null +++ b/doc/stages/references.bib @@ -0,0 +1,21 @@ +@article{Myronenko, +abstract = {Point set registration is a key component in many computer vision tasks. The goal of point set registration is to assign correspondences between two sets of points and to recover the transformation that maps one point set to the other. Multiple factors, including an unknown nonrigid spatial transformation, large dimensionality of point set, noise, and outliers, make the point set registration a challenging problem. We introduce a probabilistic method, called the Coherent Point Drift (CPD) algorithm, for both rigid and nonrigid point set registration. We consider the alignment of two point sets as a probability density estimation problem. We fit the Gaussian mixture model (GMM) centroids (representing the first point set) to the data (the second point set) by maximizing the likelihood. We force the GMM centroids to move coherently as a group to preserve the topological structure of the point sets. In the rigid case, we impose the coherence constraint by reparameterization of GMM centroid locations with rigid parameters and derive a closed form solution of the maximization step of the EM algorithm in arbitrary dimensions. In the nonrigid case, we impose the coherence constraint by regularizing the displacement field and using the variational calculus to derive the optimal transformation. We also introduce a fast algorithm that reduces the method computation complexity to linear. We test the CPD algorithm for both rigid and nonrigid transformations in the presence of noise, outliers, and missing points, where CPD shows accurate results and outperforms current state-of-the-art methods.}, +author = {Myronenko, Andriy and Song, Xubo}, +issn = {1939-3539}, +journal = {IEEE transactions on pattern analysis and machine intelligence}, +month = {dec}, +number = {12}, +pages = {2262--75}, +pmid = {20975122}, +publisher = {Institute of Electrical {\&} Electronics Engineers (IEEE)}, +title = {{Point set registration: coherent point drift.}}, +volume = {32}, +year = {2010} +} + +@article{Yuille1998, +author = {Yuille, Alan L. and Grzywacz, Norberto M.}, +journal = {Second International Conference on Computer Vision}, +title = {{The Motion Coherence Theory}}, +year = {1988} +} diff --git a/filters/HeadFilter.cpp b/filters/HeadFilter.cpp new file mode 100644 index 0000000000..1caa50db8a --- /dev/null +++ b/filters/HeadFilter.cpp @@ -0,0 +1,51 @@ +/****************************************************************************** + * Copyright (c) 2017, Bradley J Chambers (brad.chambers@gmail.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of the Andrew Bell or libLAS nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#include "HeadFilter.hpp" +#include + +namespace pdal +{ + +static PluginInfo const s_info = PluginInfo( + "filters.head", "Return N points from beginning of the point cloud.", + "http://pdal.io/stages/filters.head.html"); + +CREATE_STATIC_PLUGIN(1, 0, HeadFilter, Filter, s_info) + +std::string HeadFilter::getName() const +{ + return s_info.name; +} + +} // namespace pdal diff --git a/filters/HeadFilter.hpp b/filters/HeadFilter.hpp new file mode 100644 index 0000000000..42d65fb1e0 --- /dev/null +++ b/filters/HeadFilter.hpp @@ -0,0 +1,85 @@ +/****************************************************************************** + * Copyright (c) 2017, Bradley J Chambers (brad.chambers@gmail.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#pragma once + +#include +#include +#include + +extern "C" int32_t HeadFilter_ExitFunc(); +extern "C" PF_ExitFunc HeadFilter_InitPlugin(); + +namespace pdal +{ + +class PDAL_DLL HeadFilter : public Filter +{ +public: + HeadFilter() + { + } + + static void* create(); + static int32_t destroy(void*); + std::string getName() const; + +private: + point_count_t m_count; + + void addArgs(ProgramArgs& args) + { + args.add("count", "Number of points to return from beginning.", m_count, + point_count_t(10)); + } + + PointViewSet run(PointViewPtr view) + { + if (m_count > view->size()) + log()->get(LogLevel::Warning) + << "Requested number of points (count=" << m_count + << ") exceeds number of available points.\n"; + PointViewSet viewSet; + PointViewPtr outView = view->makeNew(); + for (PointId i = 0; i < std::min(m_count, view->size()); ++i) + outView->appendPoint(*view, i); + viewSet.insert(outView); + return viewSet; + } + + HeadFilter& operator=(const HeadFilter&); // not implemented + HeadFilter(const HeadFilter&); // not implemented +}; + +} // namespace pdal diff --git a/filters/TailFilter.cpp b/filters/TailFilter.cpp new file mode 100644 index 0000000000..f4a5d75cda --- /dev/null +++ b/filters/TailFilter.cpp @@ -0,0 +1,51 @@ +/****************************************************************************** + * Copyright (c) 2017, Bradley J Chambers (brad.chambers@gmail.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of the Andrew Bell or libLAS nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#include "TailFilter.hpp" +#include + +namespace pdal +{ + +static PluginInfo const s_info = PluginInfo( + "filters.tail", "Return N points from end of the point cloud.", + "http://pdal.io/stages/filters.tail.html"); + +CREATE_STATIC_PLUGIN(1, 0, TailFilter, Filter, s_info) + +std::string TailFilter::getName() const +{ + return s_info.name; +} + +} // namespace pdal diff --git a/filters/TailFilter.hpp b/filters/TailFilter.hpp new file mode 100644 index 0000000000..fc45ecb85d --- /dev/null +++ b/filters/TailFilter.hpp @@ -0,0 +1,86 @@ +/****************************************************************************** + * Copyright (c) 2017, Bradley J Chambers (brad.chambers@gmail.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#pragma once + +#include +#include +#include + +extern "C" int32_t TailFilter_ExitFunc(); +extern "C" PF_ExitFunc TailFilter_InitPlugin(); + +namespace pdal +{ + +class PDAL_DLL TailFilter : public Filter +{ +public: + TailFilter() + { + } + + static void* create(); + static int32_t destroy(void*); + std::string getName() const; + +private: + point_count_t m_count; + + void addArgs(ProgramArgs& args) + { + args.add("count", "Number of points to return from end.", m_count, + point_count_t(10)); + } + + PointViewSet run(PointViewPtr view) + { + if (m_count > view->size()) + log()->get(LogLevel::Warning) + << "Requested number of points (count=" << m_count + << ") exceeds number of available points.\n"; + PointViewSet viewSet; + PointViewPtr outView = view->makeNew(); + for (PointId i = view->size() - std::min(m_count, view->size()); + i < view->size(); ++i) + outView->appendPoint(*view, i); + viewSet.insert(outView); + return viewSet; + } + + TailFilter& operator=(const TailFilter&); // not implemented + TailFilter(const TailFilter&); // not implemented +}; + +} // namespace pdal diff --git a/filters/VoxelCenterNearestNeighborFilter.cpp b/filters/VoxelCenterNearestNeighborFilter.cpp new file mode 100644 index 0000000000..baa39038cd --- /dev/null +++ b/filters/VoxelCenterNearestNeighborFilter.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * Copyright (c) 2017, Bradley J Chambers (brad.chambers@gmail.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#include "VoxelCenterNearestNeighborFilter.hpp" + +#include +#include + +#include +#include +#include +#include + +namespace pdal +{ + +static PluginInfo const s_info = + PluginInfo("filters.voxelcenternearestneighbor", + "Voxel Center Nearest Neighbor Filter", + "http://pdal.io/stages/filters.voxelcenternearestneighbor.html"); + +CREATE_STATIC_PLUGIN(1, 0, VoxelCenterNearestNeighborFilter, Filter, s_info) + +std::string VoxelCenterNearestNeighborFilter::getName() const +{ + return s_info.name; +} + +void VoxelCenterNearestNeighborFilter::addArgs(ProgramArgs& args) +{ + args.add("cell", "Cell size", m_cell, 1.0); +} + +PointViewSet VoxelCenterNearestNeighborFilter::run(PointViewPtr view) +{ + BOX3D bounds; + view->calculateBounds(bounds); + + KD3Index kdi(*view); + kdi.build(); + + // Make an initial pass through the input PointView to detect populated + // voxels. + std::set> populated_voxels; + for (PointId id = 0; id < view->size(); ++id) + { + double y = view->getFieldAs(Dimension::Id::Y, id); + double x = view->getFieldAs(Dimension::Id::X, id); + double z = view->getFieldAs(Dimension::Id::Z, id); + size_t r = static_cast(floor(y - bounds.miny) / m_cell); + size_t c = static_cast(floor(x - bounds.minx) / m_cell); + size_t d = static_cast(floor(z - bounds.minz) / m_cell); + populated_voxels.emplace(std::make_tuple(r, c, d)); + } + + // Make a second pass through the populated voxels to find the nearest + // neighbor to each voxel center. + PointViewPtr output = view->makeNew(); + for (auto const& t : populated_voxels) + { + auto& r = std::get<0>(t); + auto& c = std::get<1>(t); + auto& d = std::get<2>(t); + double y = bounds.miny + (r + 0.5) * m_cell; + double x = bounds.minx + (c + 0.5) * m_cell; + double z = bounds.minz + (d + 0.5) * m_cell; + std::vector neighbors = kdi.neighbors(x, y, z, 1); + output->appendPoint(*view, neighbors[0]); + } + + PointViewSet viewSet; + viewSet.insert(output); + return viewSet; +} + +} // namespace pdal diff --git a/plugins/cpd/kernel/CpdKernel.hpp b/filters/VoxelCenterNearestNeighborFilter.hpp similarity index 68% rename from plugins/cpd/kernel/CpdKernel.hpp rename to filters/VoxelCenterNearestNeighborFilter.hpp index 19d7b4433d..4d93f8c7a3 100644 --- a/plugins/cpd/kernel/CpdKernel.hpp +++ b/filters/VoxelCenterNearestNeighborFilter.hpp @@ -1,5 +1,5 @@ /****************************************************************************** - * Copyright (c) 2014, Pete Gadomski (pete.gadomski@gmail.com) + * Copyright (c) 2017, Bradley J Chambers (brad.chambers@gmail.com) * * All rights reserved. * @@ -34,46 +34,42 @@ #pragma once -#include -#include -#include +#include +#include + +#include +#include + +extern "C" int32_t VoxelCenterNearestNeighborFilter_ExitFunc(); +extern "C" PF_ExitFunc VoxelCenterNearestNeighborFilter_InitPlugin(); namespace pdal { -class PDAL_DLL CpdKernel : public Kernel +class PointLayout; +class PointView; + +class PDAL_DLL VoxelCenterNearestNeighborFilter : public Filter { public: - static void *create(); - static int32_t destroy(void *); + VoxelCenterNearestNeighborFilter() : Filter() + { + } + + static void* create(); + static int32_t destroy(void*); std::string getName() const; - int execute(); private: - CpdKernel() : Kernel() {}; - virtual void addSwitches(ProgramArgs& args); - cpd::Matrix readFile(const std::string& filename); - - std::string m_method; - std::string m_fixed; - std::string m_moving; - std::string m_output; - BOX3D m_bounds; - - // cpd::Transform - size_t m_max_iterations; - bool m_normalize; - double m_outliers; - double m_sigma2; - double m_tolerance; + double m_cell; - // cpd::Rigid - bool m_reflections; - bool m_scale; + virtual void addArgs(ProgramArgs& args); + virtual PointViewSet run(PointViewPtr view); - // cpd::Nonrigid - double m_beta; - double m_lambda; + VoxelCenterNearestNeighborFilter& + operator=(const VoxelCenterNearestNeighborFilter&); // not implemented + VoxelCenterNearestNeighborFilter( + const VoxelCenterNearestNeighborFilter&); // not implemented }; } // namespace pdal diff --git a/filters/VoxelCentroidNearestNeighborFilter.cpp b/filters/VoxelCentroidNearestNeighborFilter.cpp new file mode 100644 index 0000000000..e8ce1564e0 --- /dev/null +++ b/filters/VoxelCentroidNearestNeighborFilter.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * Copyright (c) 2017, Bradley J Chambers (brad.chambers@gmail.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#include "VoxelCentroidNearestNeighborFilter.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace pdal +{ + +static PluginInfo const s_info = PluginInfo( + "filters.voxelcentroidnearestneighbor", + "Voxel Centroid Nearest Neighbor Filter", + "http://pdal.io/stages/filters.voxelcentroidnearestneighbor.html"); + +CREATE_STATIC_PLUGIN(1, 0, VoxelCentroidNearestNeighborFilter, Filter, s_info) + +std::string VoxelCentroidNearestNeighborFilter::getName() const +{ + return s_info.name; +} + +void VoxelCentroidNearestNeighborFilter::addArgs(ProgramArgs& args) +{ + args.add("cell", "Cell size", m_cell, 1.0); +} + +PointViewSet VoxelCentroidNearestNeighborFilter::run(PointViewPtr view) +{ + BOX3D bounds; + view->calculateBounds(bounds); + + KD3Index kdi(*view); + kdi.build(); + + // Make an initial pass through the input PointView to index PointIds by + // row, column, and depth. + std::map, std::vector> + populated_voxel_ids; + for (PointId id = 0; id < view->size(); ++id) + { + double y = view->getFieldAs(Dimension::Id::Y, id); + double x = view->getFieldAs(Dimension::Id::X, id); + double z = view->getFieldAs(Dimension::Id::Z, id); + size_t r = static_cast(floor(y - bounds.miny) / m_cell); + size_t c = static_cast(floor(x - bounds.minx) / m_cell); + size_t d = static_cast(floor(z - bounds.minz) / m_cell); + populated_voxel_ids[std::make_tuple(r, c, d)].push_back(id); + } + + // Make a second pass through the populated voxels to compute the voxel + // centroid and to find its nearest neighbor. + PointViewPtr output = view->makeNew(); + for (auto const& t : populated_voxel_ids) + { + Eigen::Vector3f centroid = eigen::computeCentroid(*view, t.second); + std::vector neighbors = + kdi.neighbors(centroid[0], centroid[1], centroid[2], 1); + output->appendPoint(*view, neighbors[0]); + } + + PointViewSet viewSet; + viewSet.insert(output); + return viewSet; +} + +} // namespace pdal diff --git a/filters/VoxelCentroidNearestNeighborFilter.hpp b/filters/VoxelCentroidNearestNeighborFilter.hpp new file mode 100644 index 0000000000..49cb949b50 --- /dev/null +++ b/filters/VoxelCentroidNearestNeighborFilter.hpp @@ -0,0 +1,75 @@ +/****************************************************************************** + * Copyright (c) 2017, Bradley J Chambers (brad.chambers@gmail.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#pragma once + +#include +#include + +#include +#include + +extern "C" int32_t VoxelCentroidNearestNeighborFilter_ExitFunc(); +extern "C" PF_ExitFunc VoxelCentroidNearestNeighborFilter_InitPlugin(); + +namespace pdal +{ + +class PointLayout; +class PointView; + +class PDAL_DLL VoxelCentroidNearestNeighborFilter : public Filter +{ +public: + VoxelCentroidNearestNeighborFilter() : Filter() + { + } + + static void* create(); + static int32_t destroy(void*); + std::string getName() const; + +private: + double m_cell; + + virtual void addArgs(ProgramArgs& args); + virtual PointViewSet run(PointViewPtr view); + + VoxelCentroidNearestNeighborFilter& + operator=(const VoxelCentroidNearestNeighborFilter&); // not implemented + VoxelCentroidNearestNeighborFilter( + const VoxelCentroidNearestNeighborFilter&); // not implemented +}; + +} // namespace pdal diff --git a/kernels/GroundKernel.cpp b/kernels/GroundKernel.cpp index 1631e10494..065ea68e28 100644 --- a/kernels/GroundKernel.cpp +++ b/kernels/GroundKernel.cpp @@ -1,47 +1,47 @@ /****************************************************************************** -* Copyright (c) 2011, Michael P. Gerlek (mpg@flaxen.com) -* Copyright (c) 2014-2015, Bradley J Chambers (brad.chambers@gmail.com) -* -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following -* conditions are met: -* -* * Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* * Redistributions in binary form must reproduce the above copyright -* notice, this list of conditions and the following disclaimer in -* the documentation and/or other materials provided -* with the distribution. -* * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the -* names of its contributors may be used to endorse or promote -* products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED -* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -* OF SUCH DAMAGE. -****************************************************************************/ + * Copyright (c) 2011, Michael P. Gerlek (mpg@flaxen.com) + * Copyright (c) 2014-2017, Bradley J Chambers (brad.chambers@gmail.com) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ #include "GroundKernel.hpp" #include #include -#include #include #include #include #include +#include #include #include @@ -51,25 +51,21 @@ namespace pdal { static PluginInfo const s_info = PluginInfo("kernels.ground", "Ground Kernel", - "http://pdal.io/apps/ground.html" ); + "http://pdal.io/apps/ground.html"); CREATE_STATIC_PLUGIN(1, 0, GroundKernel, Kernel, s_info) -std::string GroundKernel::getName() const { return s_info.name; } +std::string GroundKernel::getName() const +{ + return s_info.name; +} GroundKernel::GroundKernel() - : Kernel() - , m_inputFile("") - , m_outputFile("") - , m_maxWindowSize(33) - , m_slope(1) - , m_maxDistance(2.5) - , m_initialDistance(0.15) - , m_cellSize(1) - , m_classify(true) - , m_extract(false) - , m_approximate(false) -{} + : Kernel(), m_inputFile(""), m_outputFile(""), m_maxWindowSize(33), + m_slope(1), m_maxDistance(2.5), m_initialDistance(0.15), m_cellSize(1), + m_extract(false) +{ +} void GroundKernel::addSwitches(ProgramArgs& args) { @@ -80,34 +76,33 @@ void GroundKernel::addSwitches(ProgramArgs& args) args.add("max_distance", "Max distance", m_maxDistance, 2.5); args.add("initial_distance", "Initial distance", m_initialDistance, .15); args.add("cell_size", "Cell size", m_cellSize, 1.0); - args.add("classify", "Apply classification labels?", m_classify); args.add("extract", "extract ground returns?", m_extract); - args.add("approximate,a", "use approximate algorithm? (much faster)", - m_approximate); + args.add("approximate", "Use approximate PMF?", m_approximate, true); } int GroundKernel::execute() { PointTable table; - Stage& readerStage(makeReader(m_inputFile, "")); - Options groundOptions; groundOptions.add("max_window_size", m_maxWindowSize); groundOptions.add("slope", m_slope); groundOptions.add("max_distance", m_maxDistance); groundOptions.add("initial_distance", m_initialDistance); groundOptions.add("cell_size", m_cellSize); - groundOptions.add("classify", m_classify); - groundOptions.add("extract", m_extract); groundOptions.add("approximate", m_approximate); - Stage& groundStage = makeFilter("filters.pmf", readerStage, - groundOptions); + Options rangeOptions; + rangeOptions.add("limits", "Classification[2:2]"); + + Stage& readerStage(makeReader(m_inputFile, "")); + Stage& groundStage = makeFilter("filters.pmf", readerStage, groundOptions); - // setup the Writer and write the results - Stage& writer(makeWriter(m_outputFile, groundStage, "")); + Stage* rangeStage = &groundStage; + if (m_extract) + rangeStage = &makeFilter("filters.range", groundStage, rangeOptions); + Stage& writer(makeWriter(m_outputFile, *rangeStage, "")); writer.prepare(table); writer.execute(table); diff --git a/kernels/GroundKernel.hpp b/kernels/GroundKernel.hpp index 2c10e9fcf9..4554e62aca 100644 --- a/kernels/GroundKernel.hpp +++ b/kernels/GroundKernel.hpp @@ -1,6 +1,6 @@ /****************************************************************************** * Copyright (c) 2013, Howard Butler (hobu.inc@gmail.com) -* Copyright (c) 2014-2015, Brad Chambers (brad.chambers@gmail.com) +* Copyright (c) 2014-2017, Brad Chambers (brad.chambers@gmail.com) * * All rights reserved. * @@ -71,7 +71,6 @@ class PDAL_DLL GroundKernel : public Kernel double m_maxDistance; double m_initialDistance; double m_cellSize; - bool m_classify; bool m_extract; bool m_approximate; }; diff --git a/pdal/EigenUtils.cpp b/pdal/EigenUtils.cpp index 5526107cfd..449435dc11 100644 --- a/pdal/EigenUtils.cpp +++ b/pdal/EigenUtils.cpp @@ -92,7 +92,7 @@ Eigen::Matrix3f computeCovariance(PointView& view, std::vector ids) k++; } - return A * A.transpose(); + return A * A.transpose() / (ids.size()-1); } uint8_t computeRank(PointView& view, std::vector ids, double threshold) diff --git a/pdal/EigenUtils.hpp b/pdal/EigenUtils.hpp index 6bda076444..122c82bca5 100644 --- a/pdal/EigenUtils.hpp +++ b/pdal/EigenUtils.hpp @@ -36,6 +36,7 @@ #include #include +#include #include @@ -949,4 +950,49 @@ PDAL_DLL Eigen::MatrixXd computeSpline(Eigen::MatrixXd x, Eigen::MatrixXd y, } // namespace eigen +namespace Utils +{ + +template <> +inline bool fromString(const std::string& s, Eigen::MatrixXd& matrix) { + std::stringstream ss(s); + std::string line; + std::vector> rows; + while (std::getline(ss, line)) { + std::vector row; + std::stringstream ss(line); + double n; + while (ss >> n) { + row.push_back(n); + if (ss.peek() == ',' || ss.peek() == ' ') { + ss.ignore(); + } + } + if (!rows.empty() && rows.back().size() != row.size()) { + return false; + } + rows.push_back(row); + } + if (rows.empty()) { + return true; + } + size_t nrows = rows.size(); + size_t ncols = rows[0].size(); + matrix.resize(nrows, ncols); + for (size_t i = 0; i < nrows; ++i) { + for (size_t j = 0; j < ncols; ++j) { + matrix(i, j) = rows[i][j]; + } + } + return true; +} +} + +template <> +inline void MetadataNodeImpl::setValue(const Eigen::MatrixXd& matrix) +{ + m_type = "matrix"; + m_value = Utils::toString(matrix); +} + } // namespace pdal diff --git a/pdal/Metadata.hpp b/pdal/Metadata.hpp index e034d34ed4..70b528a0f3 100644 --- a/pdal/Metadata.hpp +++ b/pdal/Metadata.hpp @@ -488,7 +488,7 @@ class PDAL_DLL MetadataNode std::string v(Utils::escapeJSON(value())); if (m_impl->m_type == "string" || m_impl->m_type == "base64Binary" || - m_impl->m_type == "uuid") + m_impl->m_type == "uuid" || m_impl->m_type == "matrix") { std::string val("\""); val += escapeQuotes(v) + "\""; diff --git a/pdal/StageFactory.cpp b/pdal/StageFactory.cpp index 8d2ca8079f..e8395609b8 100644 --- a/pdal/StageFactory.cpp +++ b/pdal/StageFactory.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -75,7 +76,10 @@ #include #include #include +#include #include +#include +#include // readers #include @@ -258,6 +262,7 @@ StageFactory::StageFactory(bool no_plugins) PluginManager::initializePlugin(GreedyProjection_InitPlugin); PluginManager::initializePlugin(GroupByFilter_InitPlugin); PluginManager::initializePlugin(HAGFilter_InitPlugin); + PluginManager::initializePlugin(HeadFilter_InitPlugin); PluginManager::initializePlugin(IQRFilter_InitPlugin); PluginManager::initializePlugin(KDistanceFilter_InitPlugin); PluginManager::initializePlugin(LocateFilter_InitPlugin); @@ -280,7 +285,10 @@ StageFactory::StageFactory(bool no_plugins) PluginManager::initializePlugin(SortFilter_InitPlugin); PluginManager::initializePlugin(SplitterFilter_InitPlugin); PluginManager::initializePlugin(StatsFilter_InitPlugin); + PluginManager::initializePlugin(TailFilter_InitPlugin); PluginManager::initializePlugin(TransformationFilter_InitPlugin); + PluginManager::initializePlugin(VoxelCenterNearestNeighborFilter_InitPlugin); + PluginManager::initializePlugin(VoxelCentroidNearestNeighborFilter_InitPlugin); // readers PluginManager::initializePlugin(BpfReader_InitPlugin); diff --git a/plugins/cpd/CMakeLists.txt b/plugins/cpd/CMakeLists.txt index 2d27bec118..4e66b9b009 100644 --- a/plugins/cpd/CMakeLists.txt +++ b/plugins/cpd/CMakeLists.txt @@ -1,14 +1,21 @@ -find_package(Cpd 0.5 REQUIRED) +set(Cpd_VERSION 0.5) -set_package_properties(Cpd PROPERTIES - DESCRIPTION "Coherent Point Drift" - URL "https://github.com/gadomski/cpd" - TYPE OPTIONAL - PURPOSE "Register two point sets using the Coherent Point Drift algorithm" - ) +find_package(Cpd ${Cpd_VERSION} REQUIRED) +option(BUILD_PLUGIN_CPD "Build Coherent Point Drift support" ${Cpd_FOUND}) + +set(files filters/CpdFilter.cpp) +set(include_dirs "${CMAKE_CURRENT_LIST_DIR}" "${PDAL_VENDOR_DIR}/eigen") -pdal_add_plugin(cpd_kernel_lib_name kernel cpd - FILES kernel/CpdKernel.cpp +PDAL_ADD_PLUGIN(filter_libname filter cpd + FILES filters/CpdFilter.cpp LINK_WITH Cpd::Library-C++ ) -target_include_directories(${cpd_kernel_lib_name} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +target_include_directories(${filter_libname} PRIVATE "${include_dirs}") + +if(${WITH_TESTS}) + PDAL_ADD_TEST(pdal_filters_cpd_test + FILES test/CpdFilterTest.cpp + LINK_WITH ${filter_libname} + ) + target_include_directories(pdal_filters_cpd_test PRIVATE "${include_dirs}") +endif() diff --git a/plugins/cpd/filters/CpdFilter.cpp b/plugins/cpd/filters/CpdFilter.cpp new file mode 100644 index 0000000000..69b5aaaddf --- /dev/null +++ b/plugins/cpd/filters/CpdFilter.cpp @@ -0,0 +1,175 @@ +/****************************************************************************** + * Copyright (c) 2017, Peter J. Gadomski + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +namespace pdal +{ +namespace +{ +void movePoints(PointViewPtr moving, const cpd::Matrix& result) +{ + assert(moving->size() == result.rows()); + for (PointId i = 0; i < moving->size(); ++i) + { + moving->setField(Dimension::Id::X, i, result(i, 0)); + moving->setField(Dimension::Id::Y, i, result(i, 1)); + moving->setField(Dimension::Id::Z, i, result(i, 2)); + } +} + +void addMetadata(CpdFilter* filter, const cpd::Result& result) +{ + MetadataNode root = filter->getMetadata(); + root.add("sigma2", result.sigma2); + root.add("runtime", double(result.runtime.count()) / 1e6); + root.add("iterations", result.iterations); +} +} + +static PluginInfo const s_info = PluginInfo( + "filters.change", "CPD filter", "http://pdal.io/stages/filters.cpd.html"); + +CREATE_SHARED_PLUGIN(1, 0, CpdFilter, Filter, s_info); + +std::string CpdFilter::getName() const +{ + return s_info.name; +} + +void CpdFilter::addArgs(ProgramArgs& args) +{ + args.add("method", "CPD method (rigid, nonrigid, or affine)", m_method, + "rigid"); +} + +std::string CpdFilter::defaultMethod() +{ + return "rigid"; +} + +PointViewSet CpdFilter::run(PointViewPtr view) +{ + PointViewSet viewSet; + if (this->m_complete) + { + throw pdal_error( + "filters.change must have two point view inputs, no more, no less"); + } + else if (this->m_fixed) + { + log()->get(LogLevel::Debug2) << "Adding moving points\n"; + PointViewPtr result = this->change(this->m_fixed, view); + viewSet.insert(result); + this->m_complete = true; + log()->get(LogLevel::Debug2) << "filters.change complete\n"; + } + else + { + log()->get(LogLevel::Debug2) << "Adding fixed points\n"; + this->m_fixed = view; + } + return viewSet; +} + +void CpdFilter::done(PointTableRef _) +{ + if (!this->m_complete) + { + throw pdal_error( + "filters.change must have two point view inputs, no more, no less"); + } +} + +PointViewPtr CpdFilter::change(PointViewPtr fixed, PointViewPtr moving) +{ + MetadataNode root = this->getMetadata(); + root.add("method", this->m_method); + log()->get(LogLevel::Debug2) + << "filters.change running method:" << this->m_method << "\n"; + if (this->m_method == "rigid") + { + this->cpd_rigid(fixed, moving); + } + else if (this->m_method == "affine") + { + this->cpd_affine(fixed, moving); + } + else if (this->m_method == "nonrigid") + { + this->cpd_nonrigid(fixed, moving); + } + else + { + std::stringstream ss; + ss << "Invalid change detection method: " << this->m_method; + throw pdal_error(ss.str()); + } + return moving; +} + +void CpdFilter::cpd_rigid(PointViewPtr fixed, PointViewPtr moving) +{ + cpd::Matrix fixedMatrix = eigen::pointViewToEigen(*fixed); + cpd::Matrix movingMatrix = eigen::pointViewToEigen(*moving); + cpd::RigidResult result = cpd::rigid(fixedMatrix, movingMatrix); + movePoints(moving, result.points); + addMetadata(this, static_cast(result)); + MetadataNode root = getMetadata(); + root.add("transform", result.matrix()); +} + +void CpdFilter::cpd_affine(PointViewPtr fixed, PointViewPtr moving) +{ + cpd::Matrix fixedMatrix = eigen::pointViewToEigen(*fixed); + cpd::Matrix movingMatrix = eigen::pointViewToEigen(*moving); + cpd::AffineResult result = cpd::affine(fixedMatrix, movingMatrix); + movePoints(moving, result.points); + MetadataNode root = getMetadata(); + root.add("transform", result.matrix()); +} + +void CpdFilter::cpd_nonrigid(PointViewPtr fixed, PointViewPtr moving) +{ + cpd::Matrix fixedMatrix = eigen::pointViewToEigen(*fixed); + cpd::Matrix movingMatrix = eigen::pointViewToEigen(*moving); + cpd::NonrigidResult result = cpd::nonrigid(fixedMatrix, movingMatrix); + movePoints(moving, result.points); +} +} diff --git a/plugins/cpd/filters/CpdFilter.hpp b/plugins/cpd/filters/CpdFilter.hpp new file mode 100644 index 0000000000..a217788539 --- /dev/null +++ b/plugins/cpd/filters/CpdFilter.hpp @@ -0,0 +1,77 @@ +/****************************************************************************** + * Copyright (c) 2017, Peter J. Gadomski + * + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#pragma once + +#include +#include + +extern "C" int32_t CpdFilter_ExitFunc(); +extern "C" PF_ExitFunc CpdFilter_InitPlugin(); + +namespace pdal +{ + +class PDAL_DLL CpdFilter : public Filter +{ + public: + static std::string defaultMethod(); + + CpdFilter() : Filter(), m_fixed(nullptr), m_method(""), m_complete(false) + { + } + + static void* create(); + static int32_t destroy(void*); + std::string getName() const; + + virtual PointViewSet run(PointViewPtr view); + + private: + virtual void addArgs(ProgramArgs& args); + virtual void done(PointTableRef _); + + PointViewPtr change(PointViewPtr fixed, PointViewPtr moving); + void cpd_rigid(PointViewPtr fixed, PointViewPtr moving); + void cpd_affine(PointViewPtr fixed, PointViewPtr moving); + void cpd_nonrigid(PointViewPtr fixed, PointViewPtr moving); + + PointViewPtr m_fixed; + std::string m_method; + bool m_complete; + + CpdFilter& operator=(const CpdFilter&); // not implemented + CpdFilter(const CpdFilter&); // not implemented +}; +} // namespace pdal diff --git a/plugins/cpd/kernel/CpdKernel.cpp b/plugins/cpd/kernel/CpdKernel.cpp deleted file mode 100644 index b2d5a982dd..0000000000 --- a/plugins/cpd/kernel/CpdKernel.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2014, Pete Gadomski (pete.gadomski@gmail.com) - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following - * conditions are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided - * with the distribution. - * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - ****************************************************************************/ - -#include "kernel/CpdKernel.hpp" -#include - -#include -#include - -#include -#include -#include -#include -#include - -namespace pdal { - -static PluginInfo const s_info = PluginInfo( - "kernels.cpd", "CPD Kernel", "http://pdal.io/kernels/kernels.cpd.html"); - -CREATE_SHARED_PLUGIN(1, 0, CpdKernel, Kernel, s_info) - -std::string CpdKernel::getName() const { return s_info.name; } - -void CpdKernel::addSwitches(ProgramArgs& args) { - Arg& method = - args.add("method,M", "registration method (rigid, nonrigid)", m_method); - method.setPositional(); - Arg& fixed = - args.add("fixed,f", "input file containing the fixed points", m_fixed); - fixed.setPositional(); - Arg& moving = args.add("moving,m", - "input file containing the moving points, " - "i.e. the points that will be registered", - m_moving); - moving.setPositional(); - Arg& output = args.add("output,o", "output file name", m_output); - output.setPositional(); - args.add("bounds", "Extent (in XYZ) to clip output to", m_bounds); - - args.add("max-iterations", "maximum number of iterations allowed", - m_max_iterations, cpd::DEFAULT_MAX_ITERATIONS); - args.add("normalize", "whether cpd should normalize the points before running", - m_normalize, true); - args.add("outliers", "a number between zero and one that represents the tolerance for outliers", - m_outliers, cpd::DEFAULT_OUTLIERS); - args.add("sigma2", "the starting sigma2 value.", - m_sigma2, cpd::DEFAULT_SIGMA2); - args.add("tolerance", "the amount the error must change to continue iterations", - m_tolerance, cpd::DEFAULT_TOLERANCE); - - args.add("reflections", "should rigid registrations allow reflections", - m_reflections, false); - args.add("scale", "should rigid registrations allow scaling", - m_reflections, false); - - args.add("beta", "beta parameter for nonrigid registrations", - m_beta, cpd::DEFAULT_BETA); - args.add("lambda", "lambda parameter for nonrigid registrations", - m_lambda, cpd::DEFAULT_LAMBDA); -} - -cpd::Matrix CpdKernel::readFile(const std::string& filename) { - Stage& reader = makeReader(filename, ""); - - PointTable table; - PointViewSet viewSet; - if (!m_bounds.empty()) { - Options boundsOptions; - boundsOptions.add("bounds", m_bounds); - - Stage& crop = makeFilter("filters.crop", reader); - crop.setOptions(boundsOptions); - crop.prepare(table); - viewSet = crop.execute(table); - } else { - reader.prepare(table); - viewSet = reader.execute(table); - } - - return eigen::pointViewToEigen(**viewSet.begin()); -} - -int CpdKernel::execute() { - cpd::Matrix fixed = readFile(m_fixed); - cpd::Matrix moving = readFile(m_moving); - - if (fixed.rows() == 0 || moving.rows() == 0) { - throw pdal_error("No points to process."); - } - - cpd::Matrix result; - if (m_method == "rigid") { - cpd::Rigid rigid; - rigid.max_iterations(m_max_iterations) - .normalize(m_normalize) - .outliers(m_outliers) - .sigma2(m_sigma2) - .tolerance(m_tolerance); - rigid.reflections(m_reflections) - .scale(m_scale); - result = rigid.run(fixed, moving).points; - } else if (m_method == "nonrigid") { - cpd::Nonrigid nonrigid; - nonrigid.max_iterations(m_max_iterations) - .normalize(m_normalize) - .outliers(m_outliers) - .sigma2(m_sigma2) - .tolerance(m_tolerance); - nonrigid.beta(m_beta).lambda(m_lambda); - result = nonrigid.run(fixed, moving).points; - } else { - std::stringstream ss; - ss << "Invalid cpd method: " << m_method << std::endl; - throw pdal_error(ss.str()); - } - - PointTable outTable; - PointLayoutPtr outLayout(outTable.layout()); - outLayout->registerDim(Dimension::Id::X); - outLayout->registerDim(Dimension::Id::Y); - outLayout->registerDim(Dimension::Id::Z); - outLayout->registerDim(Dimension::Id::XVelocity); - outLayout->registerDim(Dimension::Id::YVelocity); - outLayout->registerDim(Dimension::Id::ZVelocity); - PointViewPtr outView(new PointView(outTable)); - - size_t rows = moving.rows(); - for (size_t i = 0; i < rows; ++i) { - outView->setField(Dimension::Id::X, i, result(i, 0)); - outView->setField(Dimension::Id::Y, i, result(i, 1)); - outView->setField(Dimension::Id::Z, i, result(i, 2)); - outView->setField(Dimension::Id::XVelocity, i, - moving(i, 0) - result(i, 0)); - outView->setField(Dimension::Id::YVelocity, i, - moving(i, 1) - result(i, 1)); - outView->setField(Dimension::Id::ZVelocity, i, - moving(i, 2) - result(i, 2)); - } - - BufferReader reader; - reader.addView(outView); - - Options writerOpts; - if (StageFactory::inferReaderDriver(m_output) == "writers.text") { - writerOpts.add("order", "X,Y,Z,XVelocity,YVelocity,ZVelocity"); - writerOpts.add("keep_unspecified", false); - } - Stage& writer = makeWriter(m_output, reader, "", writerOpts); - writer.prepare(outTable); - writer.execute(outTable); - - return 0; -} - -} // namespace pdal diff --git a/plugins/cpd/test/CpdFilterTest.cpp b/plugins/cpd/test/CpdFilterTest.cpp new file mode 100644 index 0000000000..e222824c96 --- /dev/null +++ b/plugins/cpd/test/CpdFilterTest.cpp @@ -0,0 +1,202 @@ +/****************************************************************************** + * Copyright (c) 2017, Peter J. Gadomski + * + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#include "Support.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pdal +{ +namespace +{ + +std::unique_ptr newReader() +{ + Options options; + options.add("filename", Support::datapath("las/100-points.las")); + std::unique_ptr reader(new LasReader()); + reader->setOptions(options); + return reader; +} + +std::unique_ptr newFilter() +{ + return std::unique_ptr(new CpdFilter()); +} + +void checkNoThrow(const std::string& method) +{ + auto reader1 = newReader(); + auto reader2 = newReader(); + auto filter = newFilter(); + filter->setInput(*reader1); + filter->setInput(*reader2); + + Options options; + options.add("method", method); + filter->setOptions(options); + + PointTable table; + filter->prepare(table); + ASSERT_NO_THROW(filter->execute(table)); +} + +void checkPointsEqualReader(const PointViewSet& pointViewSet, double tolerance) +{ + auto reader = newReader(); + PointTable table; + reader->prepare(table); + PointViewSet readerPointViewSet = reader->execute(table); + ASSERT_EQ(1u, readerPointViewSet.size()); + PointViewPtr expected = *readerPointViewSet.begin(); + ASSERT_EQ(1u, pointViewSet.size()); + PointViewPtr actual = *pointViewSet.begin(); + + ASSERT_EQ(expected->size(), actual->size()); + + for (PointId i = 0; i < expected->size(); ++i) + { + ASSERT_NEAR(expected->getFieldAs(Dimension::Id::X, i), + actual->getFieldAs(Dimension::Id::X, i), tolerance); + ASSERT_NEAR(expected->getFieldAs(Dimension::Id::Y, i), + actual->getFieldAs(Dimension::Id::Y, i), tolerance); + ASSERT_NEAR(expected->getFieldAs(Dimension::Id::Z, i), + actual->getFieldAs(Dimension::Id::Z, i), tolerance); + } +} +} + +TEST(CpdFilterTest, DefaultMethod) +{ + EXPECT_EQ("rigid", CpdFilter::defaultMethod()); +} + +TEST(CpdFilterTest, DefaultIdentity) +{ + auto reader1 = newReader(); + auto reader2 = newReader(); + auto filter = newFilter(); + filter->setInput(*reader1); + filter->setInput(*reader2); + + PointTable table; + filter->prepare(table); + PointViewSet viewSet = filter->execute(table); + EXPECT_EQ(1u, viewSet.size()); + EXPECT_EQ(100u, (*viewSet.begin())->size()); + + MetadataNode root = filter->getMetadata(); + EXPECT_EQ(CpdFilter::defaultMethod(), root.findChild("method").value()); + MetadataNode transform = root.findChild("transform"); + EXPECT_EQ("matrix", transform.type()); + Eigen::MatrixXd transformMatrix = transform.value(); + EXPECT_TRUE( + transformMatrix.isApprox(Eigen::MatrixXd::Identity(4, 4), 1e-4)); +} + +TEST(CpdFilterTest, RecoverTranslation) +{ + auto reader1 = newReader(); + auto reader2 = newReader(); + TransformationFilter transformationFilter; + Options transformationOptions; + transformationOptions.add("matrix", "1 0 0 1\n0 1 0 2\n0 0 1 3\n0 0 0 1"); + transformationFilter.setOptions(transformationOptions); + transformationFilter.setInput(*reader2); + + auto filter = newFilter(); + filter->setInput(*reader1); + filter->setInput(transformationFilter); + + PointTable table; + filter->prepare(table); + PointViewSet pointViewSet = filter->execute(table); + + MetadataNode root = filter->getMetadata(); + Eigen::MatrixXd transform = + root.findChild("transform").value(); + double tolerance = 1e-4; + EXPECT_NEAR(-1.0, transform(0, 3), tolerance); + EXPECT_NEAR(-2.0, transform(1, 3), tolerance); + EXPECT_NEAR(-3.0, transform(2, 3), tolerance); + checkPointsEqualReader(pointViewSet, tolerance); +} + +TEST(CpdFilterTest, TooFewInputs) +{ + auto reader = newReader(); + auto filter = newFilter(); + filter->setInput(*reader); + + PointTable table; + filter->prepare(table); + ASSERT_THROW(filter->execute(table), pdal_error); +} + +TEST(CpdFilterTest, TooManyInputs) +{ + auto reader1 = newReader(); + auto reader2 = newReader(); + auto reader3 = newReader(); + auto filter = newFilter(); + filter->setInput(*reader1); + filter->setInput(*reader2); + filter->setInput(*reader3); + + PointTable table; + filter->prepare(table); + ASSERT_THROW(filter->execute(table), pdal_error); +} + +TEST(CpdFilterTest, RigidNoThrow) +{ + checkNoThrow("rigid"); +} + +TEST(CpdFilterTest, AffineNoThrow) +{ + checkNoThrow("affine"); +} + +TEST(CpdFilterTest, NonrigidNoThrow) +{ + checkNoThrow("nonrigid"); +} +} // namespace pdal diff --git a/plugins/pcl/CMakeLists.txt b/plugins/pcl/CMakeLists.txt index 38a9d4ba63..168718948e 100644 --- a/plugins/pcl/CMakeLists.txt +++ b/plugins/pcl/CMakeLists.txt @@ -32,11 +32,13 @@ endif() set_package_properties(PCL PROPERTIES DESCRIPTION "Point Cloud Library" URL "http://pointclouds.org" TYPE RECOMMENDED - PURPOSE "Enables PCD reader/writer, PCLVisualizer, PCLBlock filter, and ground, pcl, smooth, and view kernels") + PURPOSE "Enables PCD reader/writer, PCLVisualizer, PCLBlock filter, ICP filter, and ground, pcl, smooth, and view kernels") set(INCLUDE_DIRS ${ROOT_DIR} - ${PCL_INCLUDE_DIRS}) + ${PCL_INCLUDE_DIRS} + ${CMAKE_CURRENT_LIST_DIR} + ) add_definitions(${PCL_DEFINITIONS}) if (NOT WIN32) @@ -120,6 +122,13 @@ PDAL_ADD_PLUGIN(greedyprojection_filter_libname filter greedyprojection target_include_directories(${greedyprojection_filter_libname} PRIVATE ${INCLUDE_DIRS}) +PDAL_ADD_PLUGIN(icp_filter_libname filter icp + FILES + filters/IcpFilter.cpp + LINK_WITH ${PCL_LIBRARIES}) +target_include_directories(${icp_filter_libname} PRIVATE + ${INCLUDE_DIRS}) + # # PCL Kernel # @@ -144,10 +153,18 @@ target_include_directories(${smooth_libname} PRIVATE ${PDAL_IO_DIR}) if (WITH_TESTS) - PDAL_ADD_TEST(pcltest + PDAL_ADD_TEST(pdal_filters_pcl_block_test FILES test/PCLBlockFilterTest.cpp LINK_WITH ${pcd_reader_libname} ${pcd_writer_libname} ${pclblock_libname} ${pcl_libname} ${smooth_libname} ) + + PDAL_ADD_TEST(pdal_filters_icp_test + FILES + test/IcpFilterTest.cpp + LINK_WITH + ${icp_filter_libname} + ) + target_include_directories(pdal_filters_icp_test PRIVATE ${INCLUDE_DIRS}) endif() diff --git a/plugins/pcl/filters/IcpFilter.cpp b/plugins/pcl/filters/IcpFilter.cpp new file mode 100644 index 0000000000..0784bd9605 --- /dev/null +++ b/plugins/pcl/filters/IcpFilter.cpp @@ -0,0 +1,116 @@ +/****************************************************************************** + * Copyright (c) 2017, Peter J. Gadomski + * + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#include "PCLConversions.hpp" +#include +#include +#include +#include +#include + +namespace pdal +{ + +static PluginInfo const s_info = + PluginInfo("filters.icp", "Iterative Closest Point (ICP) filter", + "http://pdal.io/stages/filters.icp.html"); + +CREATE_SHARED_PLUGIN(1, 0, IcpFilter, Filter, s_info); + +std::string IcpFilter::getName() const +{ + return s_info.name; +} + +PointViewSet IcpFilter::run(PointViewPtr view) +{ + PointViewSet viewSet; + if (this->m_fixed) + { + log()->get(LogLevel::Debug2) << "Calculating ICP\n"; + PointViewPtr result = this->icp(this->m_fixed, view); + viewSet.insert(result); + log()->get(LogLevel::Debug2) << "ICP complete\n"; + this->m_complete = true; + } + else + { + log()->get(LogLevel::Debug2) << "Adding fixed points\n"; + this->m_fixed = view; + } + return viewSet; +} + +void IcpFilter::done(PointTableRef _) +{ + if (!this->m_complete) + { + throw pdal_error( + "filters.change must have two point view inputs, no more, no less"); + } +} + +PointViewPtr IcpFilter::icp(PointViewPtr fixed, PointViewPtr moving) const +{ + typedef pcl::PointXYZ Point; + typedef pcl::PointCloud Cloud; + Cloud::Ptr fixedCloud(new Cloud()); + pclsupport::PDALtoPCD(fixed, *fixedCloud); + Cloud::Ptr movingCloud(new Cloud()); + pclsupport::PDALtoPCD(moving, *movingCloud); + pcl::IterativeClosestPoint icp; + icp.setInputCloud(movingCloud); + icp.setInputTarget(fixedCloud); + Cloud result; + icp.align(result); + + MetadataNode root = getMetadata(); + // I couldn't figure out the template-fu to get + // `MetadataNodeImpl::setValue` to work for all Eigen matrices with one + // function, so I'm just brute-forcing the cast for now. + root.add("transform", + Eigen::MatrixXd(icp.getFinalTransformation().cast())); + root.add("converged", icp.hasConverged()); + root.add("fitness", icp.getFitnessScore()); + + assert(moving->size() == result.points.size()); + for (PointId i = 0; i < moving->size(); ++i) + { + moving->setField(Dimension::Id::X, i, result.points[i].x); + moving->setField(Dimension::Id::Y, i, result.points[i].y); + moving->setField(Dimension::Id::Z, i, result.points[i].z); + } + return moving; +} +} diff --git a/plugins/pcl/filters/IcpFilter.hpp b/plugins/pcl/filters/IcpFilter.hpp new file mode 100644 index 0000000000..477958ee4b --- /dev/null +++ b/plugins/pcl/filters/IcpFilter.hpp @@ -0,0 +1,65 @@ +/****************************************************************************** + * Copyright (c) 2017, Peter J. Gadomski + * + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#pragma once + +#include +#include + +extern "C" int32_t IcpFilter_ExitFunc(); +extern "C" PF_ExitFunc IcpFilter_InitPlugin(); + +namespace pdal +{ + +class PDAL_DLL IcpFilter : public Filter +{ + public: + IcpFilter() : Filter(), m_fixed(nullptr), m_complete(false) + { + } + + static void* create(); + static int32_t destroy(void*); + std::string getName() const; + virtual PointViewSet run(PointViewPtr view); + + private: + PointViewPtr icp(PointViewPtr fixed, PointViewPtr moving) const; + virtual void done(PointTableRef _); + + PointViewPtr m_fixed; + bool m_complete; +}; +} diff --git a/plugins/pcl/test/IcpFilterTest.cpp b/plugins/pcl/test/IcpFilterTest.cpp new file mode 100644 index 0000000000..9ef6293317 --- /dev/null +++ b/plugins/pcl/test/IcpFilterTest.cpp @@ -0,0 +1,163 @@ +/****************************************************************************** + * Copyright (c) 2017, Peter J. Gadomski + * + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following + * conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + ****************************************************************************/ + +#include "Support.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace pdal +{ +namespace +{ + +std::unique_ptr newReader() +{ + Options options; + options.add("filename", Support::datapath("las/100-points.las")); + std::unique_ptr reader(new LasReader()); + reader->setOptions(options); + return reader; +} + +std::unique_ptr newFilter() +{ + return std::unique_ptr(new IcpFilter()); +} + +void checkPointsEqualReader(const PointViewSet& pointViewSet, double tolerance) +{ + auto reader = newReader(); + PointTable table; + reader->prepare(table); + PointViewSet readerPointViewSet = reader->execute(table); + ASSERT_EQ(1u, readerPointViewSet.size()); + PointViewPtr expected = *readerPointViewSet.begin(); + ASSERT_EQ(1u, pointViewSet.size()); + PointViewPtr actual = *pointViewSet.begin(); + + ASSERT_EQ(expected->size(), actual->size()); + + for (PointId i = 0; i < expected->size(); ++i) + { + ASSERT_NEAR(expected->getFieldAs(Dimension::Id::X, i), + actual->getFieldAs(Dimension::Id::X, i), tolerance); + ASSERT_NEAR(expected->getFieldAs(Dimension::Id::Y, i), + actual->getFieldAs(Dimension::Id::Y, i), tolerance); + ASSERT_NEAR(expected->getFieldAs(Dimension::Id::Z, i), + actual->getFieldAs(Dimension::Id::Z, i), tolerance); + } +} +} + +TEST(IcpFilterTest, DefaultIdentity) +{ + auto reader1 = newReader(); + auto reader2 = newReader(); + auto filter = newFilter(); + filter->setInput(*reader1); + filter->setInput(*reader2); + + PointTable table; + filter->prepare(table); + PointViewSet viewSet = filter->execute(table); + EXPECT_EQ(1u, viewSet.size()); + EXPECT_EQ(100u, (*viewSet.begin())->size()); + + MetadataNode root = filter->getMetadata(); + MetadataNode transform = root.findChild("transform"); + EXPECT_EQ("matrix", transform.type()); + Eigen::MatrixXd transformMatrix = transform.value(); + EXPECT_TRUE(transformMatrix.isApprox(Eigen::MatrixXd::Identity(4, 4), 1.0)); +} + +TEST(IcpFilterTest, RecoverTranslation) +{ + auto reader1 = newReader(); + auto reader2 = newReader(); + TransformationFilter transformationFilter; + Options transformationOptions; + transformationOptions.add("matrix", "1 0 0 1\n0 1 0 2\n0 0 1 3\n0 0 0 1"); + transformationFilter.setOptions(transformationOptions); + transformationFilter.setInput(*reader2); + + auto filter = newFilter(); + filter->setInput(*reader1); + filter->setInput(transformationFilter); + + PointTable table; + filter->prepare(table); + PointViewSet pointViewSet = filter->execute(table); + + MetadataNode root = filter->getMetadata(); + Eigen::MatrixXd transform = + root.findChild("transform").value(); + double tolerance = 1.5; + EXPECT_NEAR(-1.0, transform(0, 3), tolerance); + EXPECT_NEAR(-2.0, transform(1, 3), tolerance); + EXPECT_NEAR(-3.0, transform(2, 3), tolerance); + checkPointsEqualReader(pointViewSet, tolerance); +} + +TEST(IcpFilterTest, TooFewInputs) +{ + auto reader = newReader(); + auto filter = newFilter(); + filter->setInput(*reader); + + PointTable table; + filter->prepare(table); + ASSERT_THROW(filter->execute(table), pdal_error); +} + +TEST(IcpFilterTest, ThreeInputs) +{ + auto reader1 = newReader(); + auto reader2 = newReader(); + auto reader3 = newReader(); + auto filter = newFilter(); + filter->setInput(*reader1); + filter->setInput(*reader2); + filter->setInput(*reader3); + + PointTable table; + filter->prepare(table); + PointViewSet pointViewSet = filter->execute(table); + EXPECT_EQ(2ul, pointViewSet.size()); +} +} diff --git a/test/data/las/100-points.las b/test/data/las/100-points.las new file mode 100644 index 0000000000..eb1f6b2a4f Binary files /dev/null and b/test/data/las/100-points.las differ diff --git a/test/unit/EigenTest.cpp b/test/unit/EigenTest.cpp index ec8c9692b6..a32455ce32 100644 --- a/test/unit/EigenTest.cpp +++ b/test/unit/EigenTest.cpp @@ -250,3 +250,12 @@ TEST(EigenTest, Morphological) EXPECT_EQ(1, Fv[12]); EXPECT_EQ(0, Fv2[12]); } + +TEST(EigenTest, RoundtripString) +{ + Eigen::MatrixXd identity = Eigen::MatrixXd::Identity(4, 4); + Eigen::MatrixXd target; + Utils::fromString(Utils::toString(identity), target); + ASSERT_EQ(identity.size(), target.size()); + EXPECT_EQ(identity, target); +} diff --git a/vendor/arbiter/CMakeLists.txt b/vendor/arbiter/CMakeLists.txt index c474076bdd..fcddba9997 100644 --- a/vendor/arbiter/CMakeLists.txt +++ b/vendor/arbiter/CMakeLists.txt @@ -21,9 +21,7 @@ target_link_libraries(${PDAL_ARBITER_LIB_NAME} ) if (UNIX) target_compile_options(${PDAL_ARBITER_LIB_NAME} PRIVATE "-fPIC") else() - set_target_properties(${PDAL_ARBITER_LIB_NAME} - PROPERTIES - COMPILE_DEFINITIONS ARBITER_DLL_EXPORT) + target_compile_definitions(${PDAL_ARBITER_LIB_NAME} PUBLIC -DARBITER_DLL_EXPORT) endif() set_target_properties(${PDAL_ARBITER_LIB_NAME} PROPERTIES