diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt index 7f26197a..b6538902 100644 --- a/modules/CMakeLists.txt +++ b/modules/CMakeLists.txt @@ -8,6 +8,8 @@ add_subdirectory(odm_extract_utm) add_subdirectory(odm_georef) add_subdirectory(odm_orthophoto) add_subdirectory(odm_cleanmesh) +add_subdirectory(odm_filterpoints) + if (ODM_BUILD_SLAM) add_subdirectory(odm_slam) endif () diff --git a/modules/odm_filterpoints/CMakeLists.txt b/modules/odm_filterpoints/CMakeLists.txt new file mode 100644 index 00000000..97ed317b --- /dev/null +++ b/modules/odm_filterpoints/CMakeLists.txt @@ -0,0 +1,21 @@ +project(odm_filterpoints) +cmake_minimum_required(VERSION 2.8) + +# Add compiler options. +add_definitions(-Wall -Wextra -Wconversion -pedantic -std=c++11) + +# PDAL and jsoncpp +find_package(PDAL REQUIRED CONFIG) +include_directories(${PDAL_INCLUDE_DIRS}) +include_directories("${PROJECT_SOURCE_DIR}/../../SuperBuild/src/pdal/vendor/jsoncpp/dist") +link_directories(${PDAL_LIBRARY_DIRS}) +add_definitions(${PDAL_DEFINITIONS}) + +# Add source directory +aux_source_directory("./src" SRC_LIST) + +# Add exectuteable +add_executable(${PROJECT_NAME} ${SRC_LIST}) + +# Link +target_link_libraries(${PROJECT_NAME} jsoncpp ${PDAL_LIBRARIES}) diff --git a/modules/odm_filterpoints/src/CmdLineParser.h b/modules/odm_filterpoints/src/CmdLineParser.h new file mode 100644 index 00000000..a1fbd0e3 --- /dev/null +++ b/modules/odm_filterpoints/src/CmdLineParser.h @@ -0,0 +1,106 @@ +/* +Copyright (c) 2006, Michael Kazhdan and Matthew Bolitho +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 Johns Hopkins University 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. +*/ + +#ifndef CMD_LINE_PARSER_INCLUDED +#define CMD_LINE_PARSER_INCLUDED + +#include +#include +#include +#include +#include + +#ifdef WIN32 +int strcasecmp( const char* c1 , const char* c2 ); +#endif // WIN32 + +class cmdLineReadable +{ +public: + bool set; + char *name; + cmdLineReadable( const char *name ); + virtual ~cmdLineReadable( void ); + virtual int read( char** argv , int argc ); + virtual void writeValue( char* str ) const; +}; + +template< class Type > void cmdLineWriteValue( Type t , char* str ); +template< class Type > void cmdLineCleanUp( Type* t ); +template< class Type > Type cmdLineInitialize( void ); +template< class Type > Type cmdLineCopy( Type t ); +template< class Type > Type cmdLineStringToType( const char* str ); + +template< class Type > +class cmdLineParameter : public cmdLineReadable +{ +public: + Type value; + cmdLineParameter( const char *name ); + cmdLineParameter( const char *name , Type v ); + ~cmdLineParameter( void ); + int read( char** argv , int argc ); + void writeValue( char* str ) const; + bool expectsArg( void ) const { return true; } +}; + +template< class Type , int Dim > +class cmdLineParameterArray : public cmdLineReadable +{ +public: + Type values[Dim]; + cmdLineParameterArray( const char *name, const Type* v=NULL ); + ~cmdLineParameterArray( void ); + int read( char** argv , int argc ); + void writeValue( char* str ) const; + bool expectsArg( void ) const { return true; } +}; + +template< class Type > +class cmdLineParameters : public cmdLineReadable +{ +public: + int count; + Type *values; + cmdLineParameters( const char* name ); + ~cmdLineParameters( void ); + int read( char** argv , int argc ); + void writeValue( char* str ) const; + bool expectsArg( void ) const { return true; } +}; + +void cmdLineParse( int argc , char **argv, cmdLineReadable** params ); +char* FileExtension( char* fileName ); +char* LocalFileName( char* fileName ); +char* DirectoryName( char* fileName ); +char* GetFileExtension( const char* fileName ); +char* GetLocalFileName( const char* fileName ); +char** ReadWords( const char* fileName , int& cnt ); + +#include "CmdLineParser.inl" +#endif // CMD_LINE_PARSER_INCLUDED diff --git a/modules/odm_filterpoints/src/CmdLineParser.inl b/modules/odm_filterpoints/src/CmdLineParser.inl new file mode 100644 index 00000000..84574a57 --- /dev/null +++ b/modules/odm_filterpoints/src/CmdLineParser.inl @@ -0,0 +1,300 @@ +/* -*- C++ -*- +Copyright (c) 2006, Michael Kazhdan and Matthew Bolitho +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 Johns Hopkins University 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 + +#if defined( WIN32 ) || defined( _WIN64 ) +inline int strcasecmp( const char* c1 , const char* c2 ){ return _stricmp( c1 , c2 ); } +#endif // WIN32 || _WIN64 + +template< > void cmdLineCleanUp< int >( int* t ){ *t = 0; } +template< > void cmdLineCleanUp< float >( float* t ){ *t = 0; } +template< > void cmdLineCleanUp< double >( double* t ){ *t = 0; } +template< > void cmdLineCleanUp< char* >( char** t ){ if( *t ) free( *t ) ; *t = NULL; } +template< > int cmdLineInitialize< int >( void ){ return 0; } +template< > float cmdLineInitialize< float >( void ){ return 0.f; } +template< > double cmdLineInitialize< double >( void ){ return 0.; } +template< > char* cmdLineInitialize< char* >( void ){ return NULL; } +template< > void cmdLineWriteValue< int >( int t , char* str ){ sprintf( str , "%d" , t ); } +template< > void cmdLineWriteValue< float >( float t , char* str ){ sprintf( str , "%f" , t ); } +template< > void cmdLineWriteValue< double >( double t , char* str ){ sprintf( str , "%f" , t ); } +template< > void cmdLineWriteValue< char* >( char* t , char* str ){ if( t ) sprintf( str , "%s" , t ) ; else str[0]=0; } +template< > int cmdLineCopy( int t ){ return t; } +template< > float cmdLineCopy( float t ){ return t; } +template< > double cmdLineCopy( double t ){ return t; } +#if defined( WIN32 ) || defined( _WIN64 ) +template< > char* cmdLineCopy( char* t ){ return _strdup( t ); } +#else // !WIN32 && !_WIN64 +template< > char* cmdLineCopy( char* t ){ return strdup( t ); } +#endif // WIN32 || _WIN64 +template< > int cmdLineStringToType( const char* str ){ return atoi( str ); } +template< > float cmdLineStringToType( const char* str ){ return float( atof( str ) ); } +template< > double cmdLineStringToType( const char* str ){ return double( atof( str ) ); } +#if defined( WIN32 ) || defined( _WIN64 ) +template< > char* cmdLineStringToType( const char* str ){ return _strdup( str ); } +#else // !WIN32 && !_WIN64 +template< > char* cmdLineStringToType( const char* str ){ return strdup( str ); } +#endif // WIN32 || _WIN64 + + +///////////////////// +// cmdLineReadable // +///////////////////// +#if defined( WIN32 ) || defined( _WIN64 ) +inline cmdLineReadable::cmdLineReadable( const char *name ) : set(false) { this->name = _strdup( name ); } +#else // !WIN32 && !_WIN64 +inline cmdLineReadable::cmdLineReadable( const char *name ) : set(false) { this->name = strdup( name ); } +#endif // WIN32 || _WIN64 + +inline cmdLineReadable::~cmdLineReadable( void ){ if( name ) free( name ) ; name = NULL; } +inline int cmdLineReadable::read( char** , int ){ set = true ; return 0; } +inline void cmdLineReadable::writeValue( char* str ) const { str[0] = 0; } + +////////////////////// +// cmdLineParameter // +////////////////////// +template< class Type > cmdLineParameter< Type >::~cmdLineParameter( void ) { cmdLineCleanUp( &value ); } +template< class Type > cmdLineParameter< Type >::cmdLineParameter( const char *name ) : cmdLineReadable( name ){ value = cmdLineInitialize< Type >(); } +template< class Type > cmdLineParameter< Type >::cmdLineParameter( const char *name , Type v ) : cmdLineReadable( name ){ value = cmdLineCopy< Type >( v ); } +template< class Type > +int cmdLineParameter< Type >::read( char** argv , int argc ) +{ + if( argc>0 ) + { + cmdLineCleanUp< Type >( &value ) , value = cmdLineStringToType< Type >( argv[0] ); + set = true; + return 1; + } + else return 0; +} +template< class Type > +void cmdLineParameter< Type >::writeValue( char* str ) const { cmdLineWriteValue< Type >( value , str ); } + + +/////////////////////////// +// cmdLineParameterArray // +/////////////////////////// +template< class Type , int Dim > +cmdLineParameterArray< Type , Dim >::cmdLineParameterArray( const char *name , const Type* v ) : cmdLineReadable( name ) +{ + if( v ) for( int i=0 ; i( v[i] ); + else for( int i=0 ; i(); +} +template< class Type , int Dim > +cmdLineParameterArray< Type , Dim >::~cmdLineParameterArray( void ){ for( int i=0 ; i( values+i ); } +template< class Type , int Dim > +int cmdLineParameterArray< Type , Dim >::read( char** argv , int argc ) +{ + if( argc>=Dim ) + { + for( int i=0 ; i( values+i ) , values[i] = cmdLineStringToType< Type >( argv[i] ); + set = true; + return Dim; + } + else return 0; +} +template< class Type , int Dim > +void cmdLineParameterArray< Type , Dim >::writeValue( char* str ) const +{ + char* temp=str; + for( int i=0 ; i( values[i] , temp ); + temp = str+strlen( str ); + } +} +/////////////////////// +// cmdLineParameters // +/////////////////////// +template< class Type > +cmdLineParameters< Type >::cmdLineParameters( const char* name ) : cmdLineReadable( name ) , values(NULL) , count(0) { } +template< class Type > +cmdLineParameters< Type >::~cmdLineParameters( void ) +{ + if( values ) delete[] values; + values = NULL; + count = 0; +} +template< class Type > +int cmdLineParameters< Type >::read( char** argv , int argc ) +{ + if( values ) delete[] values; + values = NULL; + + if( argc>0 ) + { + count = atoi(argv[0]); + if( count <= 0 || argc <= count ) return 1; + values = new Type[count]; + if( !values ) return 0; + for( int i=0 ; i( argv[i+1] ); + set = true; + return count+1; + } + else return 0; +} +template< class Type > +void cmdLineParameters< Type >::writeValue( char* str ) const +{ + char* temp=str; + for( int i=0 ; i( values[i] , temp ); + temp = str+strlen( str ); + } +} + + +inline char* FileExtension( char* fileName ) +{ + char* temp = fileName; + for( unsigned int i=0 ; i=0 ; i-- ) + if( fileName[i] =='\\' ) + { + fileName[i] = 0; + return fileName; + } + fileName[0] = 0; + return fileName; +} + +inline void cmdLineParse( int argc , char **argv , cmdLineReadable** params ) +{ + while( argc>0 ) + { + if( argv[0][0]=='-' ) + { + cmdLineReadable* readable=NULL; + for( int i=0 ; params[i]!=NULL && readable==NULL ; i++ ) if( !strcasecmp( params[i]->name , argv[0]+1 ) ) readable = params[i]; + if( readable ) + { + int j = readable->read( argv+1 , argc-1 ); + argv += j , argc -= j; + } + else + { + fprintf( stderr , "[WARNING] Invalid option: %s\n" , argv[0] ); + for( int i=0 ; params[i]!=NULL ; i++ ) printf( "\t-%s\n" , params[i]->name ); + } + } + else fprintf( stderr , "[WARNING] Parameter name should be of the form -: %s\n" , argv[0] ); + ++argv , --argc; + } +} + +inline char** ReadWords(const char* fileName,int& cnt) +{ + char** names; + char temp[500]; + FILE* fp; + + fp=fopen(fileName,"r"); + if(!fp){return NULL;} + cnt=0; + while(fscanf(fp," %s ",temp)==1){cnt++;} + fclose(fp); + + names=new char*[cnt]; + if(!names){return NULL;} + + fp=fopen(fileName,"r"); + if(!fp){ + delete[] names; + cnt=0; + return NULL; + } + cnt=0; + while(fscanf(fp," %s ",temp)==1){ + names[cnt]=new char[strlen(temp)+1]; + if(!names){ + for(int j=0;j +* +* 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. +****************************************************************************/ + +// Modified to not cast to double and to use certain type identifier ("float" vs "float32") + +#include "FloatPlyReader.hpp" + +#include + +#include +#include +#include + +namespace pdal +{ + + +FloatPlyReader::FloatPlyReader() : m_vertexElt(nullptr) +{} + + +std::string FloatPlyReader::readLine() +{ + m_line.clear(); + if (m_lines.size()) + { + m_line = m_lines.top(); + m_lines.pop(); + } + else + { + do + { + std::getline(*m_stream, m_line); + } while (m_line.empty() && m_stream->good()); + } + Utils::trimTrailing(m_line); + m_linePos = Utils::extract(m_line, 0, + [](char c){ return !std::isspace(c); }); + return std::string(m_line, 0, m_linePos); +} + + +void FloatPlyReader::pushLine() +{ + m_lines.push(m_line); +} + + +std::string FloatPlyReader::nextWord() +{ + std::string s; + std::string::size_type cnt = Utils::extractSpaces(m_line, m_linePos); + m_linePos += cnt; + if (m_linePos == m_line.size()) + return s; + + cnt = Utils::extract(m_line, m_linePos, + [](char c){ return !std::isspace(c); }); + s = std::string(m_line, m_linePos, cnt); + m_linePos += cnt; + return s; +} + + +void FloatPlyReader::extractMagic() +{ + std::string first = readLine(); + if (first != "ply") + throwError("File isn't a PLY file. 'ply' not found."); + if (m_linePos != m_line.size()) + throwError("Text found following 'ply' keyword."); +} + + +void FloatPlyReader::extractEnd() +{ + std::string first = readLine(); + if (first != "end_header") + throwError("'end_header' expected but found line beginning with '" + + first + "' instead."); + if (m_linePos != m_line.size()) + throwError("Text found following 'end_header' keyword."); +} + + +void FloatPlyReader::extractFormat() +{ + std::string word = readLine(); + if (word != "format") + throwError("Expected format line not found in PLY file."); + + word = nextWord(); + if (word == "ascii") + m_format = Format::Ascii; + else if (word == "binary_big_endian") + m_format = Format::BinaryBe; + else if (word == "binary_little_endian") + m_format = Format::BinaryLe; + else + throwError("Unrecognized PLY format: '" + word + "'."); + + word = nextWord(); + if (word != "1.0") + throwError("Unsupported PLY version: '" + word + "'."); +} + + +Dimension::Type FloatPlyReader::getType(const std::string& name) +{ + static std::map types = + { + { "int8", Dimension::Type::Signed8 }, + { "uint8", Dimension::Type::Unsigned8 }, + { "int16", Dimension::Type::Signed16 }, + { "uint16", Dimension::Type::Unsigned16 }, + { "int32", Dimension::Type::Signed32 }, + { "uint32", Dimension::Type::Unsigned32 }, + { "float32", Dimension::Type::Float }, + { "float64", Dimension::Type::Double }, + + { "char", Dimension::Type::Signed8 }, + { "uchar", Dimension::Type::Unsigned8 }, + { "short", Dimension::Type::Signed16 }, + { "ushort", Dimension::Type::Unsigned16 }, + { "int", Dimension::Type::Signed32 }, + { "uint", Dimension::Type::Unsigned32 }, + { "float", Dimension::Type::Float }, + { "double", Dimension::Type::Double } + }; + + try + { + return types.at(name); + } + catch (std::out_of_range&) + {} + return Dimension::Type::None; +} + + +void FloatPlyReader::extractProperty(Element& element) +{ + std::string word = nextWord(); + Dimension::Type type = getType(word); + + if (type != Dimension::Type::None) + { + std::string name = nextWord(); + if (name.empty()) + throwError("No name for property of element '" + + element.m_name + "'."); + element.m_properties.push_back( + std::unique_ptr(new SimpleProperty(name, type))); + } + else if (word == "list") + { + if (element.m_name == "vertex") + throwError("List properties are not supported for the 'vertex' " + "element."); + + word = nextWord(); + Dimension::Type countType = getType(word); + if (countType == Dimension::Type::None) + throwError("No valid count type for list property of element '" + + element.m_name + "'."); + word = nextWord(); + Dimension::Type listType = getType(word); + if (listType == Dimension::Type::None) + throwError("No valid list type for list property of element '" + + element.m_name + "'."); + std::string name = nextWord(); + if (name.empty()) + throwError("No name for property of element '" + + element.m_name + "'."); + element.m_properties.push_back( + std::unique_ptr(new ListProperty(name, countType, + listType))); + } + else + throwError("Invalid property type '" + word + "'."); +} + + +void FloatPlyReader::extractProperties(Element& element) +{ + while (true) + { + std::string word = readLine(); + if (word == "comment" || word == "obj_info") + continue; + else if (word == "property") + extractProperty(element); + else + { + pushLine(); + break; + } + } +} + + +bool FloatPlyReader::extractElement() +{ + std::string word = readLine(); + if (word == "comment" || word == "obj_info") + return true; + else if (word == "end_header") + { + pushLine(); + return false; + } + else if (word == "element") + { + std::string name = nextWord(); + if (name.empty()) + throwError("Missing element name."); + long count = std::stol(nextWord()); + if (count < 0) + throwError("Invalid count for element '" + name + "'."); + m_elements.emplace_back(name, count); + extractProperties(m_elements.back()); + return true; + } + throwError("Invalid keyword '" + word + "' when expecting an element."); + return false; // quiet compiler +} + + +void FloatPlyReader::extractHeader() +{ + m_elements.clear(); + extractMagic(); + extractFormat(); + while (extractElement()) + ; + extractEnd(); + m_dataPos = m_stream->tellg(); + + for (Element& elt : m_elements) + if (elt.m_name == "vertex") + m_vertexElt = &elt; + if (!m_vertexElt) + throwError("Can't read PLY file without a 'vertex' element."); +} + + +std::string FloatPlyReader::getName() const +{ + return "FloatPlyReader"; +} + + +void FloatPlyReader::initialize() +{ + m_stream = Utils::openFile(m_filename, true); + if (!m_stream) + throwError("Couldn't open '" + m_filename + "'."); + extractHeader(); + Utils::closeFile(m_stream); + m_stream = nullptr; +} + + +void FloatPlyReader::addDimensions(PointLayoutPtr layout) +{ + // Override XYZ +// layout->registerDim(Dimension::Id::X); +// layout->registerDim(Dimension::Id::Y); +// layout->registerDim(Dimension::Id::Z); + + for (auto& elt : m_elements) + { + if (elt.m_name == "vertex") + { + for (auto& prop : elt.m_properties) + { + auto vprop = static_cast(prop.get()); + layout->registerOrAssignDim(vprop->m_name, vprop->m_type); + vprop->setDim( + layout->registerOrAssignDim(vprop->m_name, vprop->m_type)); + } + return; + } + } + throwError("No 'vertex' element in header."); +} + + +bool FloatPlyReader::readProperty(Property *prop, PointRef& point) +{ + if (!m_stream->good()) + return false; + prop->read(m_stream, m_format, point); + return true; +} + + +void FloatPlyReader::SimpleProperty::read(std::istream *stream, + FloatPlyReader::Format format, PointRef& point) +{ + if (format == Format::Ascii) + { + double d; + *stream >> d; + point.setField(m_dim, d); + } + else if (format == Format::BinaryLe) + { + ILeStream in(stream); + Everything e = Utils::extractDim(in, m_type); + point.setField(m_dim, m_type, &e); + } + else if (format == Format::BinaryBe) + { + IBeStream in(stream); + Everything e = Utils::extractDim(in, m_type); + point.setField(m_dim, m_type, &e); + } +} + + +// Right now we don't support list properties for point data. We just +// read the data and throw it away. +void FloatPlyReader::ListProperty::read(std::istream *stream, + FloatPlyReader::Format format, PointRef& point) +{ + if (format == Format::Ascii) + { + size_t cnt; + *stream >> cnt; + + double d; + while (cnt--) + *stream >> d; + } + else if (format == Format::BinaryLe) + { + ILeStream istream(stream); + Everything e = Utils::extractDim(istream, m_countType); + size_t cnt = (size_t)Utils::toDouble(e, m_countType); + cnt *= Dimension::size(m_listType); + istream.seek(cnt, std::ios_base::cur); + } + else if (format == Format::BinaryBe) + { + IBeStream istream(stream); + Everything e = Utils::extractDim(istream, m_countType); + size_t cnt = (size_t)Utils::toDouble(e, m_countType); + cnt *= Dimension::size(m_listType); + istream.seek(cnt, std::ios_base::cur); + } +} + + +void FloatPlyReader::readElement(Element& elt, PointRef& point) +{ + for (auto& prop : elt.m_properties) + if (!readProperty(prop.get(), point)) + throwError("Error reading data for point/element " + + std::to_string(point.pointId()) + "."); +} + + +void FloatPlyReader::ready(PointTableRef table) +{ + m_stream = Utils::openFile(m_filename, true); + if (m_stream) + m_stream->seekg(m_dataPos); + for (Element& elt : m_elements) + { + if (&elt == m_vertexElt) + break; + + // We read an element into point 0. Since the element's properties + // weren't registered as dimensions, we'll try to write the data + // to a NULL dimension, which is a noop. + // This essentially just gets us to the vertex element. + // In binary mode, this is all silliness, since we should be able + // to seek right where we want to go, but in text mode, you've got + // to go through the data. + PointRef point(table, 0); + for (PointId idx = 0; idx < elt.m_count; ++idx) + readElement(elt, point); + } + m_index = 0; +} + + +bool FloatPlyReader::processOne(PointRef& point) +{ + if (m_index < m_vertexElt->m_count) + { + readElement(*m_vertexElt, point); + m_index++; + return true; + } + return false; +} + + +// We're just reading the vertex element here. +point_count_t FloatPlyReader::read(PointViewPtr view, point_count_t num) +{ + point_count_t cnt(0); + + PointRef point(view->point(0)); + for (PointId idx = 0; idx < m_vertexElt->m_count && idx < num; ++idx) + { + point.setPointId(idx); + processOne(point); + cnt++; + } + return cnt; +} + + +void FloatPlyReader::done(PointTableRef table) +{ + Utils::closeFile(m_stream); +} + +} // namespace pdal diff --git a/modules/odm_filterpoints/src/FloatPlyReader.hpp b/modules/odm_filterpoints/src/FloatPlyReader.hpp new file mode 100644 index 00000000..1f206ed1 --- /dev/null +++ b/modules/odm_filterpoints/src/FloatPlyReader.hpp @@ -0,0 +1,156 @@ +/****************************************************************************** +* Copyright (c) 2015, 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 +#include +#include +#include + +#include + +#include +#include +#include + +namespace pdal +{ + +class PDAL_DLL FloatPlyReader : public Reader +{ +public: + std::string getName() const; + + typedef std::map DimensionMap; + + FloatPlyReader(); + +private: + enum class Format + { + Ascii, + BinaryLe, + BinaryBe + }; + + struct Property + { + Property(const std::string& name) : m_name(name) + {} + virtual ~Property() + {} + + std::string m_name; + + virtual void setDim(Dimension::Id id) + {} + virtual void read(std::istream *stream, FloatPlyReader::Format format, + PointRef& point) = 0; + }; + + struct SimpleProperty : public Property + { + SimpleProperty(const std::string& name, Dimension::Type type) : + Property(name), m_type(type), m_dim(Dimension::Id::Unknown) + {} + + Dimension::Type m_type; + Dimension::Id m_dim; + + virtual void read(std::istream *stream, FloatPlyReader::Format format, + PointRef& point) override; + virtual void setDim(Dimension::Id id) override + { m_dim = id; } + }; + + struct ListProperty : public Property + { + ListProperty(const std::string& name, Dimension::Type countType, + Dimension::Type listType) : Property(name), m_countType(countType), + m_listType(listType) + {} + + Dimension::Type m_countType; + Dimension::Type m_listType; + + virtual void read(std::istream *stream, FloatPlyReader::Format format, + PointRef& point) override; + }; + + struct Element + { + Element(const std::string name, size_t count) : + m_name(name), m_count(count) + {} + + std::string m_name; + size_t m_count; + std::vector> m_properties; + }; + + Format m_format; + std::string m_line; + std::string::size_type m_linePos; + std::stack m_lines; + std::istream *m_stream; + std::istream::streampos m_dataPos; + std::vector m_elements; + PointId m_index; + Element *m_vertexElt; + + virtual void initialize(); + virtual void addDimensions(PointLayoutPtr layout); + virtual void ready(PointTableRef table); + virtual point_count_t read(PointViewPtr view, point_count_t num); + virtual void done(PointTableRef table); + virtual bool processOne(PointRef& point); + + std::string readLine(); + void pushLine(); + std::string nextWord(); + void extractMagic(); + void extractEnd(); + void extractFormat(); + Dimension::Type getType(const std::string& name); + void extractProperty(Element& element); + void extractProperties(Element& element); + bool extractElement(); + void extractHeader(); + void readElement(Element& elt, PointRef& point); + bool readProperty(Property *prop, PointRef& point); +}; + +} // namespace pdal + diff --git a/modules/odm_filterpoints/src/Logger.h b/modules/odm_filterpoints/src/Logger.h new file mode 100644 index 00000000..723b8762 --- /dev/null +++ b/modules/odm_filterpoints/src/Logger.h @@ -0,0 +1,33 @@ +#include +#include +#include "CmdLineParser.h" + +struct Logger{ + bool verbose; + const char* outputFile; + + Logger(){ + this->verbose = false; + this->outputFile = NULL; + } + + void operator() ( const char* format , ... ) + { + if( outputFile ) + { + FILE* fp = fopen( outputFile , "a" ); + va_list args; + va_start( args , format ); + vfprintf( fp , format , args ); + fclose( fp ); + va_end( args ); + } + if( verbose ) + { + va_list args; + va_start( args , format ); + vprintf( format , args ); + va_end( args ); + } + } +}; diff --git a/modules/odm_filterpoints/src/ModifiedPlyWriter.cpp b/modules/odm_filterpoints/src/ModifiedPlyWriter.cpp new file mode 100644 index 00000000..7a1fca16 --- /dev/null +++ b/modules/odm_filterpoints/src/ModifiedPlyWriter.cpp @@ -0,0 +1,276 @@ +/****************************************************************************** +* Copyright (c) 2015, 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. +****************************************************************************/ + +// Modified to output certain property names in normalized format ("nx", "ny", ... instead of "normalx", "normaly", etc.) + +#include "ModifiedPlyWriter.hpp" + +#include +#include + +#include +#include + +namespace pdal +{ + +std::string ModifiedPlyWriter::getName() const { return "ModifiedPlyWriter"; } + + +ModifiedPlyWriter::ModifiedPlyWriter() +{} + + +void ModifiedPlyWriter::addArgs(ProgramArgs& args) +{ + args.add("filename", "Output filename", m_filename).setPositional(); + args.add("storage_mode", "PLY Storage Mode", m_format, Format::Ascii); + args.add("dims", "Dimension names", m_dimNames); + args.add("faces", "Write faces", m_faces); + m_precisionArg = &args.add("precision", "Output precision", m_precision, 3); +} + + +void ModifiedPlyWriter::prepared(PointTableRef table) +{ + if (m_precisionArg->set() && m_format != Format::Ascii) + throwError("Option 'precision' can only be set of the 'storage_mode' " + "is ascii."); + if (m_dimNames.size()) + { + for (auto& name : m_dimNames) + { + auto id = table.layout()->findDim(name); + if (id == Dimension::Id::Unknown) + throwError("Unknown dimension '" + name + "' in provided " + "dimension list."); + m_dims.push_back(id); + } + } + else + { + m_dims = table.layout()->dims(); + for (auto dim : m_dims) + m_dimNames.push_back(Utils::tolower(table.layout()->dimName(dim))); + } +} + + +std::string ModifiedPlyWriter::getType(Dimension::Type type) const +{ + static std::map types = + { + { Dimension::Type::Signed8, "char" }, + { Dimension::Type::Unsigned8, "uchar" }, + { Dimension::Type::Signed16, "short" }, + { Dimension::Type::Unsigned16, "ushort" }, + { Dimension::Type::Signed32, "int" }, + { Dimension::Type::Unsigned32, "uint" }, + { Dimension::Type::Float, "float" }, + { Dimension::Type::Double, "double" } + }; + + try + { + return types.at(type); + } + catch (std::out_of_range&) + { + throwError("Can't write dimension of type '" + + Dimension::interpretationName(type) + "'."); + } + return ""; +} + + +void ModifiedPlyWriter::writeHeader(PointLayoutPtr layout) const +{ + *m_stream << "ply" << std::endl; + *m_stream << "format " << m_format << " 1.0" << std::endl; + *m_stream << "comment Generated by odm_filterpoints" << std::endl; + *m_stream << "element vertex " << pointCount() << std::endl; + + auto ni = m_dimNames.begin(); + for (auto dim : m_dims) + { + std::string name = *ni++; + std::string typeString = getType(layout->dimType(dim)); + + // Normalize certain property names + if (name == "normalx" || name == "normal_x") name = "nx"; + if (name == "normaly" || name == "normal_y") name = "ny"; + if (name == "normalz" || name == "normal_z") name = "nz"; + + if (name == "diffuse_red") name = "red"; + if (name == "diffuse_green") name = "green"; + if (name == "diffuse_blue") name = "blue"; + + *m_stream << "property " << typeString << " " << name << std::endl; + } + if (m_faces) + { + *m_stream << "element face " << faceCount() << std::endl; + *m_stream << "property list uchar uint vertex_indices" << std::endl; + } + *m_stream << "end_header" << std::endl; +} + + +void ModifiedPlyWriter::ready(PointTableRef table) +{ + if (pointCount() > (std::numeric_limits::max)()) + throwError("Can't write PLY file. Only " + + std::to_string((std::numeric_limits::max)()) + + " points supported."); + + m_stream = Utils::createFile(m_filename, true); + if (m_format == Format::Ascii && m_precisionArg->set()) + { + *m_stream << std::fixed; + m_stream->precision(m_precision); + } + writeHeader(table.layout()); +} + + +void ModifiedPlyWriter::write(const PointViewPtr data) +{ + m_views.push_back(data); +} + + +void ModifiedPlyWriter::writeValue(PointRef& point, Dimension::Id dim, + Dimension::Type type) +{ + if (m_format == Format::Ascii) + { + double d = point.getFieldAs(dim); + *m_stream << d; + } + else if (m_format == Format::BinaryLe) + { + OLeStream out(m_stream); + Everything e; + point.getField((char *)&e, dim, type); + Utils::insertDim(out, type, e); + } + else if (m_format == Format::BinaryBe) + { + OBeStream out(m_stream); + Everything e; + point.getField((char *)&e, dim, type); + Utils::insertDim(out, type, e); + } +} + + +void ModifiedPlyWriter::writePoint(PointRef& point, PointLayoutPtr layout) +{ + for (auto it = m_dims.begin(); it != m_dims.end();) + { + Dimension::Id dim = *it; + writeValue(point, dim, layout->dimType(dim)); + ++it; + if (m_format == Format::Ascii && it != m_dims.end()) + *m_stream << " "; + } + if (m_format == Format::Ascii) + *m_stream << std::endl; +} + + +void ModifiedPlyWriter::writeTriangle(const Triangle& t, size_t offset) +{ + if (m_format == Format::Ascii) + { + *m_stream << "3 " << (t.m_a + offset) << " " << + (t.m_b + offset) << " " << (t.m_c + offset) << std::endl; + } + else if (m_format == Format::BinaryLe) + { + OLeStream out(m_stream); + unsigned char count = 3; + uint32_t a = (uint32_t)(t.m_a + offset); + uint32_t b = (uint32_t)(t.m_b + offset); + uint32_t c = (uint32_t)(t.m_c + offset); + out << count << a << b << c; + } + else if (m_format == Format::BinaryBe) + { + OBeStream out(m_stream); + unsigned char count = 3; + uint32_t a = (uint32_t)(t.m_a + offset); + uint32_t b = (uint32_t)(t.m_b + offset); + uint32_t c = (uint32_t)(t.m_c + offset); + out << count << a << b << c; + } +} + + +// Deferring write until this time allows both points and faces from multiple +// point views to be written. +void ModifiedPlyWriter::done(PointTableRef table) +{ + for (auto& v : m_views) + { + PointRef point(*v, 0); + for (PointId idx = 0; idx < v->size(); ++idx) + { + point.setPointId(idx); + writePoint(point, table.layout()); + } + } + if (m_faces) + { + PointId offset = 0; + for (auto& v : m_views) + { + TriangularMesh *mesh = v->mesh(); + if (mesh) + { + for (size_t id = 0; id < mesh->size(); ++id) + { + const Triangle& t = (*mesh)[id]; + writeTriangle(t, offset); + } + } + offset += v->size(); + } + } + Utils::closeFile(m_stream); + m_stream = nullptr; + getMetadata().addList("filename", m_filename); +} + +} // namespace pdal diff --git a/modules/odm_filterpoints/src/ModifiedPlyWriter.hpp b/modules/odm_filterpoints/src/ModifiedPlyWriter.hpp new file mode 100644 index 00000000..b69c66a9 --- /dev/null +++ b/modules/odm_filterpoints/src/ModifiedPlyWriter.hpp @@ -0,0 +1,116 @@ +/****************************************************************************** +* Copyright (c) 2015, 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 + +namespace pdal +{ + +class Triangle; + +class PDAL_DLL ModifiedPlyWriter : public Writer +{ +public: + enum class Format + { + Ascii, + BinaryLe, + BinaryBe + }; + + std::string getName() const; + + ModifiedPlyWriter(); + +private: + virtual void addArgs(ProgramArgs& args); + virtual void prepared(PointTableRef table); + virtual void ready(PointTableRef table); + virtual void write(const PointViewPtr data); + virtual void done(PointTableRef table); + + std::string getType(Dimension::Type type) const; + void writeHeader(PointLayoutPtr layout) const; + void writeValue(PointRef& point, Dimension::Id dim, Dimension::Type type); + void writePoint(PointRef& point, PointLayoutPtr layout); + void writeTriangle(const Triangle& t, size_t offset); + + std::ostream *m_stream; + std::string m_filename; + Format m_format; + bool m_faces; + StringList m_dimNames; + Dimension::IdList m_dims; + int m_precision; + Arg *m_precisionArg; + std::vector m_views; +}; + +inline std::istream& operator>>(std::istream& in, ModifiedPlyWriter::Format& f) +{ + std::string s; + std::getline(in, s); + Utils::trim(s); + Utils::tolower(s); + if (s == "ascii" || s == "default") + f = ModifiedPlyWriter::Format::Ascii; + else if (s == "little endian" || s == "binary_little_endian") + f = ModifiedPlyWriter::Format::BinaryLe; + else if (s == "big endian" || s == "binary_big_endian") + f = ModifiedPlyWriter::Format::BinaryBe; + else + in.setstate(std::ios_base::failbit); + return in; +} + + +inline std::ostream& operator<<(std::ostream& out, const ModifiedPlyWriter::Format& f) +{ + switch (f) + { + case ModifiedPlyWriter::Format::Ascii: + out << "ascii"; + break; + case ModifiedPlyWriter::Format::BinaryLe: + out << "binary_little_endian"; + break; + case ModifiedPlyWriter::Format::BinaryBe: + out << "binary_big_endian"; + break; + } + return out; +} + +} diff --git a/modules/odm_filterpoints/src/main.cpp b/modules/odm_filterpoints/src/main.cpp new file mode 100644 index 00000000..007d03c9 --- /dev/null +++ b/modules/odm_filterpoints/src/main.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include "CmdLineParser.h" +#include "Logger.h" +#include "FloatPlyReader.hpp" +#include "ModifiedPlyWriter.hpp" + +Logger logWriter; + +cmdLineParameter< char* > + InputFile( "inputFile" ) , + OutputFile( "outputFile" ); +cmdLineParameter< float > + StandardDeviation( "sd" ) , + MeanK ( "meank" ); +cmdLineReadable + Verbose( "verbose" ); + +cmdLineReadable* params[] = { + &InputFile , &OutputFile , &StandardDeviation, &MeanK, &Verbose , + NULL +}; + +void help(char *ex){ + std::cout << "Usage: " << ex << std::endl + << "\t -" << InputFile.name << " " << std::endl + << "\t -" << OutputFile.name << " " << std::endl + << "\t [-" << StandardDeviation.name << " ]" << std::endl + << "\t [-" << MeanK.name << " ]" << std::endl + + << "\t [-" << Verbose.name << "]" << std::endl; + exit(EXIT_FAILURE); +} + + +void logArgs(cmdLineReadable* params[], Logger& logWriter){ + logWriter("Running with parameters:\n"); + char str[1024]; + for( int i=0 ; params[i] ; i++ ){ + if( params[i]->set ){ + params[i]->writeValue( str ); + if( strlen( str ) ) logWriter( "\t--%s %s\n" , params[i]->name , str ); + else logWriter( "\t--%s\n" , params[i]->name ); + } + } +} + + +int main(int argc, char **argv) { + cmdLineParse( argc-1 , &argv[1] , params ); + if( !InputFile.set || !OutputFile.set ) help(argv[0]); + if( !StandardDeviation.set ) StandardDeviation.value = 2.0; + if( !MeanK.set ) MeanK.value = 8; + + logWriter.verbose = Verbose.set; + logArgs(params, logWriter); + + logWriter("Filtering point cloud...\n"); + + pdal::Options inPlyOpts; + inPlyOpts.add("filename", InputFile.value); + + pdal::PointTable table; + pdal::FloatPlyReader plyReader; + plyReader.setOptions(inPlyOpts); + + pdal::Options outlierOpts; + outlierOpts.add("method", "statistical"); + outlierOpts.add("mean_k", MeanK.value); + outlierOpts.add("multiplier", StandardDeviation.value); + + pdal::OutlierFilter outlierFilter; + outlierFilter.setInput(plyReader); + outlierFilter.setOptions(outlierOpts); + + pdal::Options rangeOpts; + rangeOpts.add("limits", "Classification![7:7]"); // Remove outliers + + pdal::RangeFilter rangeFilter; + rangeFilter.setInput(outlierFilter); + rangeFilter.setOptions(rangeOpts); + + pdal::Options outPlyOpts; + outPlyOpts.add("storage_mode", "little endian"); + outPlyOpts.add("filename", OutputFile.value); + + pdal::ModifiedPlyWriter plyWriter; + plyWriter.setOptions(outPlyOpts); + plyWriter.setInput(rangeFilter); + plyWriter.prepare(table); + plyWriter.execute(table); + + logWriter("Done!\n"); +} diff --git a/opendm/config.py b/opendm/config.py index 5f84f168..fe22e650 100644 --- a/opendm/config.py +++ b/opendm/config.py @@ -297,13 +297,10 @@ def config(): 'Default: %(default)s')) parser.add_argument('--pc-classify', - metavar='', - default='none', - choices=['none', 'smrf', 'pmf'], - help='Classify the point cloud outputs using either ' - 'a Simple Morphological Filter or a Progressive Morphological Filter. ' - 'If --dtm is set this parameter defaults to smrf. ' - 'You can control the behavior of both smrf and pmf by tweaking the --dem-* parameters. ' + action='store_true', + default=False, + help='Classify the point cloud outputs using a Simple Morphological Filter. ' + 'You can control the behavior of this option by tweaking the --dem-* parameters. ' 'Default: ' '%(default)s') @@ -317,6 +314,14 @@ def config(): default=False, help='Export the georeferenced point cloud in LAS format. Default: %(default)s') + parser.add_argument('--pc-filter', + metavar='', + type=float, + default=2.5, + help='Filters the point cloud by removing points that deviate more than N standard deviations from the local mean. Set to 0 to disable filtering.' + '\nDefault: ' + '%(default)s') + parser.add_argument('--texturing-data-term', metavar='', default='gmi', @@ -423,31 +428,6 @@ def config(): help='DSM/DTM resolution in cm / pixel.' '\nDefault: %(default)s') - parser.add_argument('--dem-maxsd', - metavar='', - type=float, - default=2.5, - help='Points that deviate more than maxsd standard deviations from the local mean ' - 'are discarded. \nDefault: ' - '%(default)s') - - parser.add_argument('--dem-initial-distance', - metavar='', - type=float, - default=0.15, - help='Used to classify ground vs non-ground points. Set this value to account for Z noise in meters. ' - 'If you have an uncertainty of around 15 cm, set this value large enough to not exclude these points. ' - 'Too small of a value will exclude valid ground points, while too large of a value will misclassify non-ground points for ground ones. ' - '\nDefault: ' - '%(default)s') - - parser.add_argument('--dem-approximate', - action='store_true', - default=False, - help='Use this tag use the approximate progressive ' - 'morphological filter, which computes DEMs faster ' - 'but is not as accurate.') - parser.add_argument('--dem-decimation', metavar='', default=1, @@ -456,17 +436,6 @@ def config(): '100 decimates ~99%% of the points. Useful for speeding up ' 'generation.\nDefault=%(default)s') - parser.add_argument('--dem-terrain-type', - metavar='', - choices=['FlatNonForest', 'FlatForest', 'ComplexNonForest', 'ComplexForest'], - default='ComplexForest', - help='One of: %(choices)s. Specifies the type of terrain. This mainly helps reduce processing time. ' - '\nFlatNonForest: Relatively flat region with little to no vegetation' - '\nFlatForest: Relatively flat region that is forested' - '\nComplexNonForest: Varied terrain with little to no vegetation' - '\nComplexForest: Varied terrain that is forested' - '\nDefault=%(default)s') - parser.add_argument('--orthophoto-resolution', metavar=' 0.0>', default=5, @@ -548,9 +517,9 @@ def config(): log.ODM_INFO('Fast orthophoto is turned on, automatically setting --skip-3dmodel') args.skip_3dmodel = True - if args.dtm and args.pc_classify == 'none': + if args.dtm and not args.pc_classify: log.ODM_INFO("DTM is turned on, automatically turning on point cloud classification") - args.pc_classify = "smrf" + args.pc_classify = True if args.skip_3dmodel and args.use_3dmesh: log.ODM_WARNING('--skip-3dmodel is set, but so is --use-3dmesh. --use_3dmesh will be ignored.') diff --git a/opendm/dem/commands.py b/opendm/dem/commands.py index 1070488d..22949e10 100644 --- a/opendm/dem/commands.py +++ b/opendm/dem/commands.py @@ -9,15 +9,11 @@ from functools import partial from . import pdal -def classify(lasFile, smrf=False, slope=1, cellsize=3, maxWindowSize=10, maxDistance=1, - approximate=False, initialDistance=0.7, verbose=False): +def classify(lasFile, slope=0.15, cellsize=1, maxWindowSize=18, verbose=False): start = datetime.now() try: - if smrf: - pdal.run_pdaltranslate_smrf(lasFile, lasFile, slope, cellsize, maxWindowSize, verbose) - else: - pdal.run_pdalground(lasFile, lasFile, slope, cellsize, maxWindowSize, maxDistance, approximate=approximate, initialDistance=initialDistance, verbose=verbose) + pdal.run_pdaltranslate_smrf(lasFile, lasFile, slope, cellsize, maxWindowSize, verbose) except: raise Exception("Error creating classified file %s" % fout) @@ -60,7 +56,6 @@ def create_dems(filenames, demtype, radius=['0.56'], gapfill=False, def create_dem(filenames, demtype, radius, decimation=None, - maxsd=None, maxz=None, products=['idw'], outdir='', suffix='', verbose=False, resolution=0.1): """ Create DEM from collection of LAS files """ start = datetime.now() @@ -75,10 +70,6 @@ def create_dem(filenames, demtype, radius, decimation=None, # JSON pipeline json = pdal.json_gdal_base(bname, products, radius, resolution) - # A DSM for meshing does not use additional filters - if demtype != 'mesh_dsm': - json = pdal.json_add_filters(json, maxsd, maxz) - if demtype == 'dsm': json = pdal.json_add_classification_filter(json, 2, equality='max') elif demtype == 'dtm': diff --git a/opendm/dem/pdal.py b/opendm/dem/pdal.py index 152d597c..d50179a9 100644 --- a/opendm/dem/pdal.py +++ b/opendm/dem/pdal.py @@ -102,75 +102,6 @@ def json_add_classification_filter(json, classification, equality="equals"): return json -def json_add_maxsd_filter(json, meank=20, thresh=3.0): - """ Add outlier Filter element and return """ - json['pipeline'].insert(0, { - 'type': 'filters.outlier', - 'method': 'statistical', - 'mean_k': meank, - 'multiplier': thresh - }) - return json - - -def json_add_maxz_filter(json, maxz): - """ Add max elevation Filter element and return """ - json['pipeline'].insert(0, { - 'type': 'filters.range', - 'limits': 'Z[:{0}]'.format(maxz) - }) - - return json - - -def json_add_maxangle_filter(json, maxabsangle): - """ Add scan angle Filter element and return """ - json['pipeline'].insert(0, { - 'type': 'filters.range', - 'limits': 'ScanAngleRank[{0}:{1}]'.format(str(-float(maxabsangle)), maxabsangle) - }) - return json - - -def json_add_scanedge_filter(json, value): - """ Add EdgeOfFlightLine Filter element and return """ - json['pipeline'].insert(0, { - 'type': 'filters.range', - 'limits': 'EdgeOfFlightLine[{0}:{0}]'.format(value) - }) - return json - - -def json_add_returnnum_filter(json, value): - """ Add ReturnNum Filter element and return """ - json['pipeline'].insert(0, { - 'type': 'filters.range', - 'limits': 'ReturnNum[{0}:{0}]'.format(value) - }) - return json - - -def json_add_filters(json, maxsd=None, maxz=None, maxangle=None, returnnum=None): - if maxsd is not None: - json = json_add_maxsd_filter(json, thresh=maxsd) - if maxz is not None: - json = json_add_maxz_filter(json, maxz) - if maxangle is not None: - json = json_add_maxangle_filter(json, maxangle) - if returnnum is not None: - json = json_add_returnnum_filter(json, returnnum) - return json - - -def json_add_crop_filter(json, wkt): - """ Add cropping polygon as Filter Element and return """ - json['pipeline'].insert(0, { - 'type': 'filters.crop', - 'polygon': wkt - }) - return json - - def is_ply_file(filename): _, ext = os.path.splitext(filename) return ext.lower() == '.ply' @@ -233,33 +164,6 @@ def run_pipeline(json, verbose=False): os.remove(jsonfile) -def run_pdalground(fin, fout, slope, cellsize, maxWindowSize, maxDistance, approximate=False, initialDistance=0.7, verbose=False): - """ Run PDAL ground """ - cmd = [ - 'pdal', - 'ground', - '-i %s' % fin, - '-o %s' % fout, - '--slope %s' % slope, - '--cell_size %s' % cellsize, - '--initial_distance %s' % initialDistance - ] - if maxWindowSize is not None: - cmd.append('--max_window_size %s' %maxWindowSize) - if maxDistance is not None: - cmd.append('--max_distance %s' %maxDistance) - - if approximate: - cmd.append('--approximate') - - if verbose: - cmd.append('--developer-debug') - print ' '.join(cmd) - print ' '.join(cmd) - out = system.run(' '.join(cmd)) - if verbose: - print out - def run_pdaltranslate_smrf(fin, fout, slope, cellsize, maxWindowSize, verbose=False): """ Run PDAL translate """ cmd = [ diff --git a/opendm/point_cloud.py b/opendm/point_cloud.py new file mode 100644 index 00000000..1fd7727e --- /dev/null +++ b/opendm/point_cloud.py @@ -0,0 +1,54 @@ +import os, sys +from opendm import system +from opendm import log +from opendm import context + +def filter(pointCloudPath, standard_deviation=2.5, meank=16, verbose=False): + """ + Filters a point cloud in place (it will replace the input file with the filtered result). + """ + if standard_deviation <= 0 or meank <= 0: + log.ODM_INFO("Skipping point cloud filtering") + return + + log.ODM_INFO("Filtering point cloud (statistical, meanK {}, standard deviation {})".format(meank, standard_deviation)) + + if not os.path.exists(pointCloudPath): + log.ODM_ERROR("{} does not exist, cannot filter point cloud. The program will now exit.".format(pointCloudPath)) + sys.exit(1) + + filter_program = os.path.join(context.odm_modules_path, 'odm_filterpoints') + if not os.path.exists(filter_program): + log.ODM_WARNING("{} program not found. Will skip filtering, but this installation should be fixed.") + return + + pc_path, pc_filename = os.path.split(pointCloudPath) + # pc_path = path/to + # pc_filename = pointcloud.ply + + basename, ext = os.path.splitext(pc_filename) + # basename = pointcloud + # ext = .ply + + tmpPointCloud = os.path.join(pc_path, "{}.tmp{}".format(basename, ext)) + + filterArgs = { + 'bin': filter_program, + 'inputFile': pointCloudPath, + 'outputFile': tmpPointCloud, + 'sd': standard_deviation, + 'meank': meank, + 'verbose': '--verbose' if verbose else '', + } + + system.run('{bin} -inputFile {inputFile} ' + '-outputFile {outputFile} ' + '-sd {sd} ' + '-meank {meank} {verbose} '.format(**filterArgs)) + + # Remove input file, swap temp file + if os.path.exists(tmpPointCloud): + os.remove(pointCloudPath) + os.rename(tmpPointCloud, pointCloudPath) + else: + log.ODM_WARNING("{} not found, filtering has failed.".format(tmpPointCloud)) diff --git a/scripts/odm_dem.py b/scripts/odm_dem.py index 8592f8e5..f35a1c51 100644 --- a/scripts/odm_dem.py +++ b/scripts/odm_dem.py @@ -39,45 +39,33 @@ class ODMDEMCell(ecto.Cell): (args.rerun_from is not None and 'odm_dem' in args.rerun_from) - log.ODM_INFO('Classify: ' + str(args.pc_classify != "none")) + log.ODM_INFO('Classify: ' + str(args.pc_classify)) log.ODM_INFO('Create DSM: ' + str(args.dsm)) log.ODM_INFO('Create DTM: ' + str(args.dtm)) log.ODM_INFO('DEM input file {0} found: {1}'.format(tree.odm_georeferencing_model_laz, str(las_model_found))) - # Setup terrain parameters - terrain_params_map = { - 'flatnonforest': (1, 3), - 'flatforest': (1, 2), - 'complexnonforest': (5, 2), - 'complexforest': (10, 2) - } - terrain_params = terrain_params_map[args.dem_terrain_type.lower()] - slope, cellsize = terrain_params + slope, cellsize = (0.15, 1) # define paths and create working directories odm_dem_root = tree.path('odm_dem') if not io.dir_exists(odm_dem_root): system.mkdir_p(odm_dem_root) - if args.pc_classify != "none" and las_model_found: + if args.pc_classify and las_model_found: pc_classify_marker = os.path.join(odm_dem_root, 'pc_classify_done.txt') if not io.file_exists(pc_classify_marker) or rerun_cell: - log.ODM_INFO("Classifying {} using {}".format(tree.odm_georeferencing_model_laz, args.pc_classify)) + log.ODM_INFO("Classifying {} using Simple Morphological Filter".format(tree.odm_georeferencing_model_laz)) commands.classify(tree.odm_georeferencing_model_laz, - args.pc_classify == "smrf", slope, cellsize, - approximate=args.dem_approximate, - initialDistance=args.dem_initial_distance, verbose=args.verbose ) + with open(pc_classify_marker, 'w') as f: - f.write('Classify: {}\n'.format(args.pc_classify)) + f.write('Classify: smrf\n') f.write('Slope: {}\n'.format(slope)) f.write('Cellsize: {}\n'.format(cellsize)) - f.write('Approximate: {}\n'.format(args.dem_approximate)) - f.write('InitialDistance: {}\n'.format(args.dem_initial_distance)) # Do we need to process anything here? if (args.dsm or args.dtm) and las_model_found: @@ -105,7 +93,6 @@ class ODMDEMCell(ecto.Cell): gapfill=True, outdir=odm_dem_root, resolution=resolution / 100.0, - maxsd=args.dem_maxsd, decimation=args.dem_decimation, verbose=args.verbose, max_workers=get_max_concurrency_for_dem(args.max_concurrency,tree.odm_georeferencing_model_laz) diff --git a/scripts/odm_georeferencing.py b/scripts/odm_georeferencing.py index 59ffa09c..134e1ed0 100644 --- a/scripts/odm_georeferencing.py +++ b/scripts/odm_georeferencing.py @@ -9,6 +9,7 @@ from opendm import types from opendm import system from opendm import context from opendm.cropper import Cropper +from opendm import point_cloud class ODMGeoreferencingCell(ecto.Cell): diff --git a/scripts/run_opensfm.py b/scripts/run_opensfm.py index d12c4baa..5a37d6d8 100644 --- a/scripts/run_opensfm.py +++ b/scripts/run_opensfm.py @@ -1,11 +1,13 @@ import ecto import sys +import os from opendm import log from opendm import io from opendm import system from opendm import context from opendm import gsd +from opendm import point_cloud class ODMOpenSfMCell(ecto.Cell): def declare_params(self, params): @@ -170,6 +172,9 @@ class ODMOpenSfMCell(ecto.Cell): if args.fast_orthophoto: system.run('PYTHONPATH=%s %s/bin/opensfm export_ply --no-cameras %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) + + # Filter + point_cloud.filter(os.path.join(tree.opensfm, 'reconstruction.ply'), standard_deviation=args.pc_filter, verbose=args.verbose) elif args.use_opensfm_dense: # Undistort images at full scale in JPG # (TODO: we could compare the size of the PNGs if they are < than depthmap_resolution @@ -179,6 +184,8 @@ class ODMOpenSfMCell(ecto.Cell): system.run('PYTHONPATH=%s %s/bin/opensfm compute_depthmaps %s' % (context.pyopencv_path, context.opensfm_path, tree.opensfm)) + # Filter + point_cloud.filter(tree.opensfm_model, standard_deviation=args.pc_filter, verbose=args.verbose) else: log.ODM_WARNING('Found a valid OpenSfM reconstruction file in: %s' % tree.opensfm_reconstruction) diff --git a/scripts/smvs.py b/scripts/smvs.py index 2ce9f58f..8090f310 100644 --- a/scripts/smvs.py +++ b/scripts/smvs.py @@ -4,6 +4,7 @@ from opendm import log from opendm import io from opendm import system from opendm import context +from opendm import point_cloud class ODMSmvsCell(ecto.Cell): @@ -91,6 +92,9 @@ class ODMSmvsCell(ecto.Cell): old_file = smvs_files[-1] if not (io.rename_file(old_file, tree.smvs_model)): log.ODM_WARNING("File %s does not exist, cannot be renamed. " % old_file) + + # Filter + point_cloud.filter(tree.smvs_model, standard_deviation=args.pc_filter, verbose=args.verbose) else: log.ODM_WARNING("Cannot find a valid point cloud (smvs-XX.ply) in %s. Check the console output for errors." % tree.smvs) else: