Merge branch 'odm_output_improvements' of ssh://git.masseranolabs.com:/home/odm/OpenDroneMap into add-poissonrecon

pull/889/head
Dakota Benjamin 2018-07-06 10:01:33 -04:00
commit 4a81684d60
32 zmienionych plików z 875 dodań i 1720 usunięć

Wyświetl plik

@ -60,7 +60,7 @@ RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Clean Superbuild
RUN rm -rf /code/SuperBuild/download /code/SuperBuild/src/vtk7 /code/SuperBuild/src/opencv /code/SuperBuild/src/pcl /code/SuperBuild/src/pdal /code/SuperBuild/src/opengv /code/SuperBuild/src/mvstexturing /code/SuperBuild/src/ceres /code/SuperBuild/build/vtk7 /code/SuperBuild/build/opencv
RUN rm -rf /code/SuperBuild/download /code/SuperBuild/src/opencv /code/SuperBuild/src/pcl /code/SuperBuild/src/pdal /code/SuperBuild/src/opengv /code/SuperBuild/src/mvstexturing /code/SuperBuild/src/ceres /code/SuperBuild/build/opencv
# Entry point
ENTRYPOINT ["python", "/code/run.py", "code"]

Wyświetl plik

@ -95,17 +95,6 @@ option(ODM_BUILD_Ceres "Force to build Ceres library" OFF)
SETUP_EXTERNAL_PROJECT(Ceres ${ODM_Ceres_Version} ${ODM_BUILD_Ceres})
# ---------------------------------------------------------------------------------------------
# VTK7
# We need to build VTK from sources because Debian packages
# are built with DVTK_SMP_IMPLEMENTATION_TYPE set to
# "Sequential" which means no multithread support.
set(ODM_VTK7_Version 7.1.1)
option(ODM_BUILD_VTK7 "Force to build VTK7 library" OFF)
SETUP_EXTERNAL_PROJECT(VTK7 ${ODM_VTK7_Version} ${ODM_BUILD_VTK7})
# ---------------------------------------------------------------------------------------------
# Hexer
#
@ -140,22 +129,23 @@ endforeach()
## Add smvs Build
externalproject_add(smvs
GIT_REPOSITORY https://github.com/flanggut/smvs.git
GIT_TAG 6a7d0c095aa66ab98c5b285c2bc04e34d8993353
externalproject_add(mve
GIT_REPOSITORY https://github.com/simonfuhrmann/mve.git
GIT_TAG 2106a5b0aef61a7f744551510b1b4c5d8e3be594
UPDATE_COMMAND ""
SOURCE_DIR ${SB_SOURCE_DIR}/elibs/smvs
SOURCE_DIR ${SB_SOURCE_DIR}/elibs/mve
CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
BUILD_COMMAND make
INSTALL_COMMAND ""
)
externalproject_add(mve
GIT_REPOSITORY https://github.com/simonfuhrmann/mve.git
GIT_TAG 2106a5b0aef61a7f744551510b1b4c5d8e3be594
externalproject_add(smvs
DEPENDS mve
GIT_REPOSITORY https://github.com/flanggut/smvs.git
GIT_TAG 6a7d0c095aa66ab98c5b285c2bc04e34d8993353
UPDATE_COMMAND ""
SOURCE_DIR ${SB_SOURCE_DIR}/elibs/mve
SOURCE_DIR ${SB_SOURCE_DIR}/elibs/smvs
CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
BUILD_COMMAND make

Wyświetl plik

@ -1,29 +0,0 @@
set(_proj_name vtk7)
set(_SB_BINARY_DIR "${SB_BINARY_DIR}/${_proj_name}")
ExternalProject_Add(${_proj_name}
PREFIX ${_SB_BINARY_DIR}
TMP_DIR ${_SB_BINARY_DIR}/tmp
STAMP_DIR ${_SB_BINARY_DIR}/stamp
#--Download step--------------
DOWNLOAD_DIR ${SB_DOWNLOAD_DIR}/${_proj_name}
URL https://github.com/Kitware/VTK/archive/v7.1.1.zip
#--Update/Patch step----------
UPDATE_COMMAND ""
#--Configure step-------------
SOURCE_DIR ${SB_SOURCE_DIR}/${_proj_name}
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX:PATH=${SB_INSTALL_DIR}
-DVTK_SMP_IMPLEMENTATION_TYPE=TBB
-DCMAKE_BUILD_TYPE=Release
-DVTK_Group_Rendering=OFF
-DBUILD_TESTING=OFF
#--Build step-----------------
BINARY_DIR ${_SB_BINARY_DIR}
#--Install step---------------
INSTALL_DIR ${SB_INSTALL_DIR}
#--Output logging-------------
LOG_DOWNLOAD OFF
LOG_CONFIGURE OFF
LOG_BUILD OFF
)

Wyświetl plik

@ -61,7 +61,7 @@ RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Clean Superbuild
RUN rm -rf /code/SuperBuild/download /code/SuperBuild/src/vtk7 /code/SuperBuild/src/opencv /code/SuperBuild/src/pcl /code/SuperBuild/src/pdal /code/SuperBuild/src/opengv /code/SuperBuild/src/mvstexturing /code/SuperBuild/src/ceres /code/SuperBuild/build/vtk7 /code/SuperBuild/build/opencv
RUN rm -rf /code/SuperBuild/download /code/SuperBuild/src/opencv /code/SuperBuild/src/pcl /code/SuperBuild/src/pdal /code/SuperBuild/src/opengv /code/SuperBuild/src/mvstexturing /code/SuperBuild/src/ceres /code/SuperBuild/build/opencv
# Entry point
ENTRYPOINT ["python", "/code/run.py", "code"]

Wyświetl plik

@ -7,7 +7,7 @@ endif()
add_subdirectory(odm_extract_utm)
add_subdirectory(odm_georef)
add_subdirectory(odm_orthophoto)
add_subdirectory(odm_25dmeshing)
add_subdirectory(odm_cleanmesh)
if (ODM_BUILD_SLAM)
add_subdirectory(odm_slam)
endif ()

Wyświetl plik

@ -1,31 +0,0 @@
#include "Logger.hpp"
Logger::Logger(bool isPrintingInCout) : isPrintingInCout_(isPrintingInCout)
{
}
Logger::~Logger()
{
}
void Logger::printToFile(std::string filePath)
{
std::ofstream file(filePath.c_str(), std::ios::binary);
file << logStream_.str();
file.close();
}
bool Logger::isPrintingInCout() const
{
return isPrintingInCout_;
}
void Logger::setIsPrintingInCout(bool isPrintingInCout)
{
isPrintingInCout_ = isPrintingInCout;
}

Wyświetl plik

@ -1,67 +0,0 @@
#pragma once
#include <string>
#include <sstream>
#include <fstream>
#include <iostream>
/*!
* \brief The Logger class is used to store program messages in a log file.
* \details By using the << operator while printInCout is set, the class writes both to
* cout and to file, if the flag is not set, output is written to file only.
*/
class Logger
{
public:
/*!
* \brief Logger Contains functionality for printing and displaying log information.
* \param printInCout Flag toggling if operator << also writes to cout.
*/
Logger(bool isPrintingInCout = true);
/*!
* \brief Destructor.
*/
~Logger();
/*!
* \brief print Prints the contents of the log to file.
* \param filePath Path specifying where to write the log.
*/
void printToFile(std::string filePath);
/*!
* \brief isPrintingInCout Check if console printing flag is set.
* \return Console printing flag.
*/
bool isPrintingInCout() const;
/*!
* \brief setIsPrintingInCout Set console printing flag.
* \param isPrintingInCout Value, if true, messages added to the log are also printed in cout.
*/
void setIsPrintingInCout(bool isPrintingInCout);
/*!
* Operator for printing messages to log and in the standard output stream if desired.
*/
template<class T>
friend Logger& operator<< (Logger &log, T t)
{
// If console printing is enabled.
if (log.isPrintingInCout_)
{
std::cout << t;
std::cout.flush();
}
// Write to log.
log.logStream_ << t;
return log;
}
private:
bool isPrintingInCout_; /*!< If flag is set, log is printed in cout and written to the log. */
std::stringstream logStream_; /*!< Stream for storing the log. */
};

Wyświetl plik

@ -1,450 +0,0 @@
#include "Odm25dMeshing.hpp"
int Odm25dMeshing::run(int argc, char **argv) {
log << logFilePath << "\n";
// If no arguments were passed, print help and return early.
if (argc <= 1) {
printHelp();
return EXIT_SUCCESS;
}
try {
parseArguments(argc, argv);
loadPointCloud();
buildMesh();
} catch (const Odm25dMeshingException& e) {
log.setIsPrintingInCout(true);
log << e.what() << "\n";
log.printToFile(logFilePath);
log << "For more detailed information, see log file." << "\n";
return EXIT_FAILURE;
} catch (const std::exception& e) {
log.setIsPrintingInCout(true);
log << "Error in OdmMeshing:\n";
log << e.what() << "\n";
log.printToFile(logFilePath);
log << "For more detailed information, see log file." << "\n";
return EXIT_FAILURE;
}
log.printToFile(logFilePath);
return EXIT_SUCCESS;
}
void Odm25dMeshing::loadPointCloud() {
log << "Loading point cloud... ";
try{
std::ifstream ss(inputFile, std::ios::binary);
if (ss.fail()) throw Odm25dMeshingException("Failed to open " + inputFile);
PlyFile file;
file.parse_header(ss);
std::shared_ptr<PlyData> vertices = file.request_properties_from_element("vertex", { "x", "y", "z" });
file.read(ss);
const size_t numVerticesBytes = vertices->buffer.size_bytes();
struct float3 { float x, y, z; };
struct double3 { double x, y, z; };
if (vertices->t == tinyply::Type::FLOAT32) {
std::vector<float3> verts(vertices->count);
std::memcpy(verts.data(), vertices->buffer.get(), numVerticesBytes);
for (float3 &v : verts){
points->InsertNextPoint(v.x, v.y, v.z);
}
}else if (vertices->t == tinyply::Type::FLOAT64) {
std::vector<double3> verts(vertices->count);
std::memcpy(verts.data(), vertices->buffer.get(), numVerticesBytes);
for (double3 &v : verts){
points->InsertNextPoint(v.x, v.y, v.z);
}
}else{
throw Odm25dMeshingException("Invalid data type (only float32 and float64 are supported): " + std::to_string((int)vertices->t));
}
}
catch (const std::exception & e)
{
throw Odm25dMeshingException("Error while loading point cloud: " + std::string(e.what()));
}
log << "loaded " << points->GetNumberOfPoints() << " points\n";
}
void Odm25dMeshing::buildMesh(){
vtkThreadedImageAlgorithm::SetGlobalDefaultEnableSMP(true);
log << "Remove outliers... ";
vtkSmartPointer<vtkPolyData> polyPoints =
vtkSmartPointer<vtkPolyData>::New();
polyPoints->SetPoints(points);
vtkSmartPointer<vtkOctreePointLocator> locator = vtkSmartPointer<vtkOctreePointLocator>::New();
vtkSmartPointer<vtkRadiusOutlierRemoval> radiusRemoval =
vtkSmartPointer<vtkRadiusOutlierRemoval>::New();
radiusRemoval->SetInputData(polyPoints);
radiusRemoval->SetLocator(locator);
radiusRemoval->SetRadius(20); // 20 meters
radiusRemoval->SetNumberOfNeighbors(2);
vtkSmartPointer<vtkStatisticalOutlierRemoval> statsRemoval =
vtkSmartPointer<vtkStatisticalOutlierRemoval>::New();
statsRemoval->SetInputConnection(radiusRemoval->GetOutputPort());
statsRemoval->SetLocator(locator);
statsRemoval->SetSampleSize(neighbors);
statsRemoval->SetStandardDeviationFactor(1.5);
statsRemoval->GenerateOutliersOff();
statsRemoval->Update();
log << (radiusRemoval->GetNumberOfPointsRemoved() + statsRemoval->GetNumberOfPointsRemoved()) << " points removed\n";
vtkSmartPointer<vtkPoints> cleanedPoints = statsRemoval->GetOutput()->GetPoints();
statsRemoval = nullptr;
radiusRemoval = nullptr;
polyPoints = nullptr;
log << "Squash point cloud to plane... ";
vtkSmartPointer<vtkFloatArray> elevation = vtkSmartPointer<vtkFloatArray>::New();
elevation->SetName("elevation");
elevation->SetNumberOfComponents(1);
double p[2];
for (vtkIdType i = 0; i < cleanedPoints->GetNumberOfPoints(); i++){
cleanedPoints->GetPoint(i, p);
elevation->InsertNextValue(p[2]);
p[2] = 0.0;
cleanedPoints->SetPoint(i, p);
}
log << "OK\n";
vtkSmartPointer<vtkPolyData> polydataToProcess =
vtkSmartPointer<vtkPolyData>::New();
polydataToProcess->SetPoints(cleanedPoints);
polydataToProcess->GetPointData()->SetScalars(elevation);
const float NODATA = -9999;
double *bounds = polydataToProcess->GetBounds();
double centerX = polydataToProcess->GetCenter()[0];
double centerY = polydataToProcess->GetCenter()[1];
double centerZ = polydataToProcess->GetCenter()[2];
double extentX = bounds[1] - bounds[0];
double extentY = bounds[3] - bounds[2];
if (resolution == 0.0){
resolution = (double)maxVertexCount / (sqrt(extentX * extentY) * 75.0);
log << "Automatically set resolution to " << std::fixed << resolution << "\n";
}
int width = ceil(extentX * resolution);
int height = ceil(extentY * resolution);
log << "Plane extentX: " << extentX <<
", extentY: " << extentY << "\n";
double planeCenter[3];
planeCenter[0] = centerX;
planeCenter[1] = centerY;
planeCenter[2] = centerZ;
vtkSmartPointer<vtkPlaneSource> plane =
vtkSmartPointer<vtkPlaneSource>::New();
plane->SetResolution(width, height);
plane->SetOrigin(0.0, 0.0, 0.0);
plane->SetPoint1(extentX, 0.0, 0.0);
plane->SetPoint2(0.0, extentY, 0);
plane->SetCenter(planeCenter);
plane->SetNormal(0.0, 0.0, 1.0);
vtkSmartPointer<vtkShepardKernel> shepardKernel =
vtkSmartPointer<vtkShepardKernel>::New();
shepardKernel->SetPowerParameter(2.0);
shepardKernel->SetKernelFootprintToNClosest();
shepardKernel->SetNumberOfPoints(neighbors);
vtkSmartPointer<vtkImageData> image =
vtkSmartPointer<vtkImageData>::New();
image->SetDimensions(width, height, 1);
log << "DSM size is " << width << "x" << height << " (" << ceil(width * height * sizeof(float) * 1e-6) << " MB) \n";
image->AllocateScalars(VTK_FLOAT, 1);
log << "Point interpolation using shepard's kernel... ";
vtkSmartPointer<vtkPointInterpolator> interpolator =
vtkSmartPointer<vtkPointInterpolator>::New();
interpolator->SetInputConnection(plane->GetOutputPort());
interpolator->SetSourceData(polydataToProcess);
interpolator->SetKernel(shepardKernel);
interpolator->SetLocator(locator);
interpolator->SetNullValue(NODATA);
interpolator->Update();
vtkSmartPointer<vtkPolyData> interpolatedPoly =
interpolator->GetPolyDataOutput();
log << "OK\nTransfering interpolation results to DSM... ";
interpolator = nullptr;
polydataToProcess = nullptr;
elevation = nullptr;
cleanedPoints = nullptr;
plane = nullptr;
shepardKernel = nullptr;
locator = nullptr;
vtkSmartPointer<vtkFloatArray> interpolatedElevation =
vtkFloatArray::SafeDownCast(interpolatedPoly->GetPointData()->GetArray("elevation"));
for (int i = 0; i < width; i++){
for (int j = 0; j < height; j++){
float* pixel = static_cast<float*>(image->GetScalarPointer(i,j,0));
vtkIdType cellId = interpolatedPoly->GetCell(j * width + i)->GetPointId(0);
pixel[0] = interpolatedElevation->GetValue(cellId);
}
}
log << "OK\nMedian filter...";
vtkSmartPointer<vtkImageMedian3D> medianFilter =
vtkSmartPointer<vtkImageMedian3D>::New();
medianFilter->SetInputData(image);
medianFilter->SetKernelSize(
std::max(1.0, resolution),
std::max(1.0, resolution),
1);
medianFilter->Update();
log << "OK\n";
// double diffuseIterations = std::max(1.0, resolution / 2.0);
// vtkSmartPointer<vtkImageAnisotropicDiffusion2D> diffuse1 =
// vtkSmartPointer<vtkImageAnisotropicDiffusion2D>::New();
// diffuse1->SetInputConnection(medianFilter->GetOutputPort());
// diffuse1->FacesOn();
// diffuse1->EdgesOn();
// diffuse1->CornersOn();
// diffuse1->SetDiffusionFactor(1); // Full strength
// diffuse1->GradientMagnitudeThresholdOn();
// diffuse1->SetDiffusionThreshold(0.2); // Don't smooth jumps in elevation > than 0.20m
// diffuse1->SetNumberOfIterations(diffuseIterations);
// diffuse1->Update();
if (outputDsmFile != ""){
log << "Saving DSM to file... ";
vtkSmartPointer<vtkTIFFWriter> tiffWriter =
vtkSmartPointer<vtkTIFFWriter>::New();
tiffWriter->SetFileName(outputDsmFile.c_str());
tiffWriter->SetInputData(medianFilter->GetOutput());
tiffWriter->Write();
log << "OK\n";
}
log << "Triangulate... ";
vtkSmartPointer<vtkGreedyTerrainDecimation> terrain =
vtkSmartPointer<vtkGreedyTerrainDecimation>::New();
terrain->SetErrorMeasureToNumberOfTriangles();
terrain->SetNumberOfTriangles(maxVertexCount * 2); // Approximate
terrain->SetInputData(medianFilter->GetOutput());
terrain->BoundaryVertexDeletionOn();
log << "OK\nTransform... ";
vtkSmartPointer<vtkTransform> transform =
vtkSmartPointer<vtkTransform>::New();
transform->Translate(-extentX / 2.0 + centerX,
-extentY / 2.0 + centerY, 0);
transform->Scale(extentX / width, extentY / height, 1);
vtkSmartPointer<vtkTransformFilter> transformFilter =
vtkSmartPointer<vtkTransformFilter>::New();
transformFilter->SetInputConnection(terrain->GetOutputPort());
transformFilter->SetTransform(transform);
log << "OK\n";
log << "Saving mesh to file... ";
vtkSmartPointer<vtkPLYWriter> plyWriter =
vtkSmartPointer<vtkPLYWriter>::New();
plyWriter->SetFileName(outputFile.c_str());
plyWriter->SetInputConnection(transformFilter->GetOutputPort());
plyWriter->SetFileTypeToASCII();
plyWriter->Write();
log << "OK\n";
#ifdef SUPPORTDEBUGWINDOW
if (showDebugWindow){
vtkSmartPointer<vtkPolyDataMapper> mapper =
vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputConnection(transformFilter->GetOutputPort());
mapper->SetScalarRange(150, 170);
// vtkSmartPointer<vtkDataSetMapper> mapper =
// vtkSmartPointer<vtkDataSetMapper>::New();
// mapper->SetInputData(image);
// mapper->SetScalarRange(150, 170);
vtkSmartPointer<vtkActor> actor =
vtkSmartPointer<vtkActor>::New();
actor->SetMapper(mapper);
actor->GetProperty()->SetPointSize(5);
vtkSmartPointer<vtkRenderer> renderer =
vtkSmartPointer<vtkRenderer>::New();
vtkSmartPointer<vtkRenderWindow> renderWindow =
vtkSmartPointer<vtkRenderWindow>::New();
renderWindow->AddRenderer(renderer);
vtkSmartPointer<vtkRenderWindowInteractor> renderWindowInteractor =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
renderWindowInteractor->SetRenderWindow(renderWindow);
renderer->AddActor(actor);
renderer->SetBackground(0.1804,0.5451,0.3412); // Sea green
renderWindow->Render();
renderWindowInteractor->Start();
}
#endif
}
void Odm25dMeshing::parseArguments(int argc, char **argv) {
for (int argIndex = 1; argIndex < argc; ++argIndex) {
// The argument to be parsed.
std::string argument = std::string(argv[argIndex]);
if (argument == "-help") {
printHelp();
exit(0);
} else if (argument == "-verbose") {
log.setIsPrintingInCout(true);
} else if (argument == "-maxVertexCount" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) throw Odm25dMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
std::stringstream ss(argv[argIndex]);
ss >> maxVertexCount;
if (ss.bad()) throw Odm25dMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
maxVertexCount = std::max<unsigned int>(maxVertexCount, 0);
log << "Vertex count was manually set to: " << maxVertexCount << "\n";
} else if (argument == "-resolution" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) throw Odm25dMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
std::stringstream ss(argv[argIndex]);
ss >> resolution;
if (ss.bad()) throw Odm25dMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
resolution = std::min<double>(100000, std::max<double>(resolution, 0));
log << "Resolution was manually set to: " << resolution << "\n";
} else if (argument == "-neighbors" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) throw Odm25dMeshingException("Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
std::stringstream ss(argv[argIndex]);
ss >> neighbors;
if (ss.bad()) throw Odm25dMeshingException("Argument '" + argument + "' has a bad value (wrong type).");
neighbors = std::min<unsigned int>(1000, std::max<unsigned int>(neighbors, 1));
log << "Neighbors was manually set to: " << neighbors << "\n";
} else if (argument == "-inputFile" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) {
throw Odm25dMeshingException(
"Argument '" + argument
+ "' expects 1 more input following it, but no more inputs were provided.");
}
inputFile = std::string(argv[argIndex]);
std::ifstream testFile(inputFile.c_str(), std::ios::binary);
if (!testFile.is_open()) {
throw Odm25dMeshingException(
"Argument '" + argument + "' has a bad value. (file not accessible)");
}
testFile.close();
log << "Reading point cloud at: " << inputFile << "\n";
} else if (argument == "-outputFile" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) {
throw Odm25dMeshingException(
"Argument '" + argument + "' expects 1 more input following it, but no more inputs were provided.");
}
outputFile = std::string(argv[argIndex]);
std::ofstream testFile(outputFile.c_str());
if (!testFile.is_open()) {
throw Odm25dMeshingException(
"Argument '" + argument + "' has a bad value.");
}
testFile.close();
log << "Writing output to: " << outputFile << "\n";
}else if (argument == "-outputDsmFile" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) {
throw Odm25dMeshingException(
"Argument '" + argument
+ "' expects 1 more input following it, but no more inputs were provided.");
}
outputDsmFile = std::string(argv[argIndex]);
std::ofstream testFile(outputDsmFile.c_str());
if (!testFile.is_open()) {
throw Odm25dMeshingException(
"Argument '" + argument + "' has a bad value. (file not accessible)");
}
testFile.close();
log << "Saving DSM output to: " << outputDsmFile << "\n";
} else if (argument == "-showDebugWindow") {
showDebugWindow = true;
} else if (argument == "-logFile" && argIndex < argc) {
++argIndex;
if (argIndex >= argc) {
throw Odm25dMeshingException(
"Argument '" + argument
+ "' expects 1 more input following it, but no more inputs were provided.");
}
logFilePath = std::string(argv[argIndex]);
std::ofstream testFile(outputFile.c_str());
if (!testFile.is_open()) {
throw Odm25dMeshingException(
"Argument '" + argument + "' has a bad value.");
}
testFile.close();
log << "Writing log information to: " << logFilePath << "\n";
} else {
printHelp();
throw Odm25dMeshingException("Unrecognised argument '" + argument + "'");
}
}
}
void Odm25dMeshing::printHelp() {
bool printInCoutPop = log.isPrintingInCout();
log.setIsPrintingInCout(true);
log << "Usage: odm_25dmeshing -inputFile [plyFile] [optional-parameters]\n";
log << "Create a 2.5D mesh from a point cloud. "
<< "The program requires a path to an input PLY point cloud file, all other input parameters are optional.\n\n";
log << " -inputFile <path> to PLY point cloud\n"
<< " -outputFile <path> where the output PLY 2.5D mesh should be saved (default: " << outputFile << ")\n"
<< " -outputDsmFile <path> Optionally output the Digital Surface Model (DSM) computed for generating the mesh. (default: " << outputDsmFile << ")\n"
<< " -logFile <path> log file path (default: " << logFilePath << ")\n"
<< " -verbose whether to print verbose output (default: " << (printInCoutPop ? "true" : "false") << ")\n"
<< " -maxVertexCount <0 - N> Maximum number of vertices in the output mesh. The mesh might have fewer vertices, but will not exceed this limit. (default: " << maxVertexCount << ")\n"
<< " -neighbors <1 - 1000> Number of nearest neighbors to consider when doing shepard's interpolation and outlier removal. Higher values lead to smoother meshes but take longer to process. (default: " << neighbors << ")\n"
<< " -resolution <0 - N> Size of the interpolated digital surface model (DSM) used for deriving the 2.5D mesh, expressed in pixels per meter unit. When set to zero, the program automatically attempts to find a good value based on the point cloud extent and target vertex count. (default: " << resolution << ")\n"
<< "\n";
log.setIsPrintingInCout(printInCoutPop);
}

Wyświetl plik

@ -1,111 +0,0 @@
#pragma once
//#define SUPPORTDEBUGWINDOW 1
#ifdef SUPPORTDEBUGWINDOW
#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkDataSetMapper.h>
#endif
#include <vtkShepardKernel.h>
#include <vtkPointData.h>
#include <vtkImageData.h>
#include <vtkGreedyTerrainDecimation.h>
#include <vtkPLYWriter.h>
#include <vtkSmartPointer.h>
#include <vtkFloatArray.h>
#include <vtkOctreePointLocator.h>
#include <vtkPointInterpolator.h>
#include <vtkPlaneSource.h>
#include <vtkTransform.h>
#include <vtkTransformFilter.h>
#include <vtkImageAnisotropicDiffusion2D.h>
#include <vtkTIFFWriter.h>
#include <vtkStatisticalOutlierRemoval.h>
#include <vtkImageMedian3D.h>
#include <vtkRadiusOutlierRemoval.h>
// For compatibility with new VTK generic data arrays
#ifdef vtkGenericDataArray_h
#define InsertNextTupleValue InsertNextTypedTuple
#endif
#include <cstring>
#include "tinyply.h"
using namespace tinyply;
#include "Logger.hpp"
class Odm25dMeshing {
public:
Odm25dMeshing() :
log(false) {};
~Odm25dMeshing() {};
/*!
* \brief run Runs the meshing functionality using the provided input arguments.
* For a list of accepted arguments, please see the main page documentation or
* call the program with parameter "-help".
* \param argc Application argument count.
* \param argv Argument values.
* \return 0 If successful.
*/
int run(int argc, char **argv);
private:
/*!
* \brief parseArguments Parses command line arguments.
* \param argc Application argument count.
* \param argv Argument values.
*/
void parseArguments(int argc, char** argv);
/*!
* \brief printHelp Prints help, explaining usage. Can be shown by calling the program with argument: "-help".
*/
void printHelp();
void loadPointCloud();
void buildMesh();
Logger log;
std::string inputFile = "";
std::string outputFile = "odm_25dmesh.ply";
std::string logFilePath = "odm_25dmeshing_log.txt";
int maxVertexCount = 100000;
double resolution = 0;
unsigned int neighbors = 24;
std::string outputDsmFile = "";
bool showDebugWindow = false;
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
};
class Odm25dMeshingException: public std::exception {
public:
Odm25dMeshingException() :
message("Error in Odm25dMeshing") {
}
Odm25dMeshingException(std::string msgInit) :
message("Error in Odm25dMeshing:\n" + msgInit) {
}
~Odm25dMeshingException() throw () {
}
virtual const char* what() const throw () {
return message.c_str();
}
private:
std::string message; /**< The error message **/
};

Wyświetl plik

@ -1,31 +0,0 @@
/*
OpenDroneMap - https://www.opendronemap.org
Copyright (C) 2017 OpenDroneMap Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Odm25dMeshing.hpp"
/*!
* \mainpage main OpenDroneMap 2.5D Meshing Module
*
* The OpenDroneMap 2.5D Meshing Module generates a 2.5D mesh using a constrained
* delaunay triangulation from any point cloud (points with corresponding normals).
*/
int main(int argc, char** argv)
{
Odm25dMeshing om;
return om.run(argc, argv);
}

Wyświetl plik

@ -1,621 +0,0 @@
// This software is in the public domain. Where that dedication is not
// recognized, you are granted a perpetual, irrevocable license to copy,
// distribute, and modify this file as you see fit.
// Authored in 2015 by Dimitri Diakopoulos (http://www.dimitridiakopoulos.com)
// https://github.com/ddiakopoulos/tinyply
// Version 2.0
#include "tinyply.h"
#include <algorithm>
#include <functional>
#include <type_traits>
#include <iostream>
#include <cstring>
using namespace tinyply;
using namespace std;
//////////////////
// Endian Utils //
//////////////////
template<typename T> T endian_swap(const T & v) { return v; }
template<> inline uint16_t endian_swap(const uint16_t & v) { return (v << 8) | (v >> 8); }
template<> inline uint32_t endian_swap(const uint32_t & v) { return (v << 24) | ((v << 8) & 0x00ff0000) | ((v >> 8) & 0x0000ff00) | (v >> 24); }
template<> inline uint64_t endian_swap(const uint64_t & v)
{
return (((v & 0x00000000000000ffLL) << 56) |
((v & 0x000000000000ff00LL) << 40) |
((v & 0x0000000000ff0000LL) << 24) |
((v & 0x00000000ff000000LL) << 8) |
((v & 0x000000ff00000000LL) >> 8) |
((v & 0x0000ff0000000000LL) >> 24) |
((v & 0x00ff000000000000LL) >> 40) |
((v & 0xff00000000000000LL) >> 56));
}
template<> inline int16_t endian_swap(const int16_t & v) { uint16_t r = endian_swap(*(uint16_t*)&v); return *(int16_t*)&r; }
template<> inline int32_t endian_swap(const int32_t & v) { uint32_t r = endian_swap(*(uint32_t*)&v); return *(int32_t*)&r; }
template<> inline int64_t endian_swap(const int64_t & v) { uint64_t r = endian_swap(*(uint64_t*)&v); return *(int64_t*)&r; }
inline float endian_swap_float(const uint32_t & v) { union { float f; uint32_t i; }; i = endian_swap(v); return f; }
inline double endian_swap_double(const uint64_t & v) { union { double d; uint64_t i; }; i = endian_swap(v); return d; }
/////////////////////////////
// Internal Implementation //
/////////////////////////////
inline Type property_type_from_string(const std::string & t)
{
if (t == "int8" || t == "char") return Type::INT8;
else if (t == "uint8" || t == "uchar") return Type::UINT8;
else if (t == "int16" || t == "short") return Type::INT16;
else if (t == "uint16" || t == "ushort") return Type::UINT16;
else if (t == "int32" || t == "int") return Type::INT32;
else if (t == "uint32" || t == "uint") return Type::UINT32;
else if (t == "float32" || t == "float") return Type::FLOAT32;
else if (t == "float64" || t == "double") return Type::FLOAT64;
return Type::INVALID;
}
struct PlyFile::PlyFileImpl
{
struct PlyCursor
{
size_t byteOffset;
size_t totalSizeBytes;
};
struct ParsingHelper
{
std::shared_ptr<PlyData> data;
std::shared_ptr<PlyCursor> cursor;
};
std::map<std::string, ParsingHelper> userData;
bool isBinary = false;
bool isBigEndian = false;
std::vector<PlyElement> elements;
std::vector<std::string> comments;
std::vector<std::string> objInfo;
void read(std::istream & is);
void write(std::ostream & os, bool isBinary);
std::shared_ptr<PlyData> request_properties_from_element(const std::string & elementKey, const std::initializer_list<std::string> propertyKeys);
void add_properties_to_element(const std::string & elementKey, const std::initializer_list<std::string> propertyKeys, const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount);
size_t read_property_binary(const Type t, void * dest, size_t & destOffset, std::istream & is);
size_t read_property_ascii(const Type t, void * dest, size_t & destOffset, std::istream & is);
size_t skip_property_binary(const PlyProperty & property, std::istream & is);
size_t skip_property_ascii(const PlyProperty & property, std::istream & is);
bool parse_header(std::istream & is);
void parse_data(std::istream & is, bool firstPass);
void read_header_format(std::istream & is);
void read_header_element(std::istream & is);
void read_header_property(std::istream & is);
void read_header_text(std::string line, std::vector<std::string> & place, int erase = 0);
void write_header(std::ostream & os);
void write_ascii_internal(std::ostream & os);
void write_binary_internal(std::ostream & os);
void write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset);
void write_property_binary(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset);
};
//////////////////
// PLY Property //
//////////////////
PlyProperty::PlyProperty(std::istream & is) : isList(false)
{
std::string type;
is >> type;
if (type == "list")
{
std::string countType;
is >> countType >> type;
listType = property_type_from_string(countType);
isList = true;
}
propertyType = property_type_from_string(type);
is >> name;
}
/////////////////
// PLY Element //
/////////////////
PlyElement::PlyElement(std::istream & is)
{
is >> name >> size;
}
///////////
// Utils //
///////////
std::string make_key(const std::string & a, const std::string & b)
{
return (a + "-" + b);
}
template<typename T> void ply_cast(void * dest, const char * src, bool be)
{
*(static_cast<T *>(dest)) = (be) ? endian_swap(*(reinterpret_cast<const T *>(src))) : *(reinterpret_cast<const T *>(src));
}
template<typename T> void ply_cast_float(void * dest, const char * src, bool be)
{
*(static_cast<T *>(dest)) = (be) ? endian_swap_float(*(reinterpret_cast<const uint32_t *>(src))) : *(reinterpret_cast<const T *>(src));
}
template<typename T> void ply_cast_double(void * dest, const char * src, bool be)
{
*(static_cast<T *>(dest)) = (be) ? endian_swap_double(*(reinterpret_cast<const uint64_t *>(src))) : *(reinterpret_cast<const T *>(src));
}
template<typename T> T ply_read_ascii(std::istream & is)
{
T data;
is >> data;
return data;
}
template<typename T> void ply_cast_ascii(void * dest, std::istream & is)
{
*(static_cast<T *>(dest)) = ply_read_ascii<T>(is);
}
size_t find_element(const std::string & key, const std::vector<PlyElement> & list)
{
for (size_t i = 0; i < list.size(); i++) if (list[i].name == key) return i;
return -1;
}
size_t find_property(const std::string & key, const std::vector<PlyProperty> & list)
{
for (size_t i = 0; i < list.size(); ++i) if (list[i].name == key) return i;
return -1;
}
//////////////
// PLY File //
//////////////
bool PlyFile::PlyFileImpl::parse_header(std::istream & is)
{
std::string line;
while (std::getline(is, line))
{
std::istringstream ls(line);
std::string token;
ls >> token;
if (token == "ply" || token == "PLY" || token == "") continue;
else if (token == "comment") read_header_text(line, comments, 8);
else if (token == "format") read_header_format(ls);
else if (token == "element") read_header_element(ls);
else if (token == "property") read_header_property(ls);
else if (token == "obj_info") read_header_text(line, objInfo, 9);
else if (token == "end_header") break;
else return false;
}
return true;
}
void PlyFile::PlyFileImpl::read_header_text(std::string line, std::vector<std::string>& place, int erase)
{
place.push_back((erase > 0) ? line.erase(0, erase) : line);
}
void PlyFile::PlyFileImpl::read_header_format(std::istream & is)
{
std::string s;
(is >> s);
if (s == "binary_little_endian") isBinary = true;
else if (s == "binary_big_endian") isBinary = isBigEndian = true;
}
void PlyFile::PlyFileImpl::read_header_element(std::istream & is)
{
elements.emplace_back(is);
}
void PlyFile::PlyFileImpl::read_header_property(std::istream & is)
{
elements.back().properties.emplace_back(is);
}
size_t PlyFile::PlyFileImpl::skip_property_binary(const PlyProperty & p, std::istream & is)
{
static std::vector<char> skip(PropertyTable[p.propertyType].stride);
if (p.isList)
{
size_t listSize = 0;
size_t dummyCount = 0;
read_property_binary(p.listType, &listSize, dummyCount, is);
for (size_t i = 0; i < listSize; ++i) is.read(skip.data(), PropertyTable[p.propertyType].stride);
return listSize * PropertyTable[p.propertyType].stride; // in bytes
}
else
{
is.read(skip.data(), PropertyTable[p.propertyType].stride);
return PropertyTable[p.propertyType].stride;
}
}
size_t PlyFile::PlyFileImpl::skip_property_ascii(const PlyProperty & p, std::istream & is)
{
std::string skip;
if (p.isList)
{
size_t listSize = 0;
size_t dummyCount = 0;
read_property_ascii(p.listType, &listSize, dummyCount, is);
for (size_t i = 0; i < listSize; ++i) is >> skip;
return listSize * PropertyTable[p.propertyType].stride; // in bytes
}
else
{
is >> skip;
return PropertyTable[p.propertyType].stride;
}
}
size_t PlyFile::PlyFileImpl::read_property_binary(const Type t, void * dest, size_t & destOffset, std::istream & is)
{
destOffset += PropertyTable[t].stride;
std::vector<char> src(PropertyTable[t].stride);
is.read(src.data(), PropertyTable[t].stride);
switch (t)
{
case Type::INT8: ply_cast<int8_t>(dest, src.data(), isBigEndian); break;
case Type::UINT8: ply_cast<uint8_t>(dest, src.data(), isBigEndian); break;
case Type::INT16: ply_cast<int16_t>(dest, src.data(), isBigEndian); break;
case Type::UINT16: ply_cast<uint16_t>(dest, src.data(), isBigEndian); break;
case Type::INT32: ply_cast<int32_t>(dest, src.data(), isBigEndian); break;
case Type::UINT32: ply_cast<uint32_t>(dest, src.data(), isBigEndian); break;
case Type::FLOAT32: ply_cast_float<float>(dest, src.data(), isBigEndian); break;
case Type::FLOAT64: ply_cast_double<double>(dest, src.data(), isBigEndian); break;
case Type::INVALID: throw std::invalid_argument("invalid ply property");
}
return PropertyTable[t].stride;
}
size_t PlyFile::PlyFileImpl::read_property_ascii(const Type t, void * dest, size_t & destOffset, std::istream & is)
{
destOffset += PropertyTable[t].stride;
switch (t)
{
case Type::INT8: *((int8_t *)dest) = ply_read_ascii<int32_t>(is); break;
case Type::UINT8: *((uint8_t *)dest) = ply_read_ascii<uint32_t>(is); break;
case Type::INT16: ply_cast_ascii<int16_t>(dest, is); break;
case Type::UINT16: ply_cast_ascii<uint16_t>(dest, is); break;
case Type::INT32: ply_cast_ascii<int32_t>(dest, is); break;
case Type::UINT32: ply_cast_ascii<uint32_t>(dest, is); break;
case Type::FLOAT32: ply_cast_ascii<float>(dest, is); break;
case Type::FLOAT64: ply_cast_ascii<double>(dest, is); break;
case Type::INVALID: throw std::invalid_argument("invalid ply property");
}
return PropertyTable[t].stride;
}
void PlyFile::PlyFileImpl::write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset)
{
switch (t)
{
case Type::INT8: os << static_cast<int32_t>(*reinterpret_cast<int8_t*>(src)); break;
case Type::UINT8: os << static_cast<uint32_t>(*reinterpret_cast<uint8_t*>(src)); break;
case Type::INT16: os << *reinterpret_cast<int16_t*>(src); break;
case Type::UINT16: os << *reinterpret_cast<uint16_t*>(src); break;
case Type::INT32: os << *reinterpret_cast<int32_t*>(src); break;
case Type::UINT32: os << *reinterpret_cast<uint32_t*>(src); break;
case Type::FLOAT32: os << *reinterpret_cast<float*>(src); break;
case Type::FLOAT64: os << *reinterpret_cast<double*>(src); break;
case Type::INVALID: throw std::invalid_argument("invalid ply property");
}
os << " ";
srcOffset += PropertyTable[t].stride;
}
void PlyFile::PlyFileImpl::write_property_binary(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset)
{
os.write((char *)src, PropertyTable[t].stride);
srcOffset += PropertyTable[t].stride;
}
void PlyFile::PlyFileImpl::read(std::istream & is)
{
// Parse but only get the data size
parse_data(is, true);
std::vector<std::shared_ptr<PlyData>> buffers;
for (auto & entry : userData) buffers.push_back(entry.second.data);
// Since group-requested properties share the same cursor, we need to find unique cursors so we only allocate once
std::sort(buffers.begin(), buffers.end());
buffers.erase(std::unique(buffers.begin(), buffers.end()), buffers.end());
// Not great, but since we sorted by ptrs on PlyData, need to remap back onto its cursor in the userData table
for (auto & b : buffers)
{
for (auto & entry : userData)
{
if (entry.second.data == b && b->buffer.get() == nullptr)
{
b->buffer = Buffer(entry.second.cursor->totalSizeBytes);
}
}
}
// Populate the data
parse_data(is, false);
}
void PlyFile::PlyFileImpl::write(std::ostream & os, bool _isBinary)
{
if (_isBinary) write_binary_internal(os);
else write_ascii_internal(os);
}
void PlyFile::PlyFileImpl::write_binary_internal(std::ostream & os)
{
isBinary = true;
write_header(os);
for (auto & e : elements)
{
for (size_t i = 0; i < e.size; ++i)
{
for (auto & p : e.properties)
{
auto & helper = userData[make_key(e.name, p.name)];
if (p.isList)
{
uint8_t listSize[4] = {0, 0, 0, 0};
std::memcpy(listSize, &p.listCount, sizeof(uint32_t));
size_t dummyCount = 0;
write_property_binary(p.listType, os, listSize, dummyCount);
for (int j = 0; j < p.listCount; ++j)
{
write_property_binary(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset);
}
}
else
{
write_property_binary(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset);
}
}
}
}
}
void PlyFile::PlyFileImpl::write_ascii_internal(std::ostream & os)
{
write_header(os);
for (auto & e : elements)
{
for (size_t i = 0; i < e.size; ++i)
{
for (auto & p : e.properties)
{
auto & helper = userData[make_key(e.name, p.name)];
if (p.isList)
{
os << p.listCount << " ";
for (int j = 0; j < p.listCount; ++j)
{
write_property_ascii(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset);
}
}
else
{
write_property_ascii(p.propertyType, os, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset);
}
}
os << "\n";
}
}
}
void PlyFile::PlyFileImpl::write_header(std::ostream & os)
{
const std::locale & fixLoc = std::locale("C");
os.imbue(fixLoc);
os << "ply\n";
if (isBinary) os << ((isBigEndian) ? "format binary_big_endian 1.0" : "format binary_little_endian 1.0") << "\n";
else os << "format ascii 1.0\n";
for (const auto & comment : comments) os << "comment " << comment << "\n";
for (auto & e : elements)
{
os << "element " << e.name << " " << e.size << "\n";
for (const auto & p : e.properties)
{
if (p.isList)
{
os << "property list " << PropertyTable[p.listType].str << " "
<< PropertyTable[p.propertyType].str << " " << p.name << "\n";
}
else
{
os << "property " << PropertyTable[p.propertyType].str << " " << p.name << "\n";
}
}
}
os << "end_header\n";
}
// Returns the size (in bytes)
std::shared_ptr<PlyData> PlyFile::PlyFileImpl::request_properties_from_element(const std::string & elementKey, const std::initializer_list<std::string> propertyKeys)
{
// All requested properties in the userDataTable share the same cursor (thrown into the same flat array)
ParsingHelper helper;
helper.data = std::make_shared<PlyData>();
helper.data->count = 0;
helper.data->t = Type::INVALID;
helper.cursor = std::make_shared<PlyCursor>();
helper.cursor->byteOffset = 0;
helper.cursor->totalSizeBytes = 0;
if (elements.size() == 0) throw std::runtime_error("parsed header had no elements defined. malformed file?");
if (!propertyKeys.size()) throw std::invalid_argument("`propertyKeys` argument is empty");
if (elementKey.size() == 0) throw std::invalid_argument("`elementKey` argument is empty");
const int elementIndex = find_element(elementKey, elements);
// Sanity check if the user requested element is in the pre-parsed header
if (elementIndex >= 0)
{
// We found the element
const PlyElement & element = elements[elementIndex];
helper.data->count = element.size;
// Find each of the keys
for (auto key : propertyKeys)
{
const int propertyIndex = find_property(key, element.properties);
if (propertyIndex >= 0)
{
// We found the property
const PlyProperty & property = element.properties[propertyIndex];
helper.data->t = property.propertyType; // hmm....
auto result = userData.insert(std::pair<std::string, ParsingHelper>(make_key(element.name, property.name), helper));
if (result.second == false) throw std::invalid_argument("element-property key has already been requested: " + make_key(element.name, property.name));
}
else throw std::invalid_argument("one of the property keys was not found in the header: " + key);
}
}
else throw std::invalid_argument("the element key was not found in the header: " + elementKey);
return helper.data;
}
void PlyFile::PlyFileImpl::add_properties_to_element(const std::string & elementKey, const std::initializer_list<std::string> propertyKeys, const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount)
{
ParsingHelper helper;
helper.data = std::make_shared<PlyData>();
helper.data->count = count;
helper.data->t = type;
helper.data->buffer = Buffer(data);
helper.cursor = std::make_shared<PlyCursor>();
helper.cursor->byteOffset = 0;
helper.cursor->totalSizeBytes = 0;
auto create_property_on_element = [&](PlyElement & e)
{
for (auto key : propertyKeys)
{
PlyProperty newProp = (listType == Type::INVALID) ? PlyProperty(type, key) : PlyProperty(listType, type, key, listCount);
/* auto result = */userData.insert(std::pair<std::string, ParsingHelper>(make_key(elementKey, key), helper));
e.properties.push_back(newProp);
}
};
int idx = find_element(elementKey, elements);
if (idx >= 0)
{
PlyElement & e = elements[idx];
create_property_on_element(e);
}
else
{
PlyElement newElement = (listType == Type::INVALID) ? PlyElement(elementKey, count / propertyKeys.size()) : PlyElement(elementKey, count / listCount);
create_property_on_element(newElement);
elements.push_back(newElement);
}
}
void PlyFile::PlyFileImpl::parse_data(std::istream & is, bool firstPass)
{
std::function<size_t(const Type t, void * dest, size_t & destOffset, std::istream & is)> read;
std::function<size_t(const PlyProperty & p, std::istream & is)> skip;
const auto start = is.tellg();
if (isBinary)
{
read = [&](const Type t, void * dest, size_t & destOffset, std::istream & _is) { return read_property_binary(t, dest, destOffset, _is); };
skip = [&](const PlyProperty & p, std::istream & _is) { return skip_property_binary(p, _is); };
}
else
{
read = [&](const Type t, void * dest, size_t & destOffset, std::istream & _is) { return read_property_ascii(t, dest, destOffset, _is); };
skip = [&](const PlyProperty & p, std::istream & _is) { return skip_property_ascii(p, _is); };
}
for (auto & element : elements)
{
for (size_t count = 0; count < element.size; ++count)
{
for (auto & property : element.properties)
{
auto cursorIt = userData.find(make_key(element.name, property.name));
if (cursorIt != userData.end())
{
auto & helper = cursorIt->second;
if (!firstPass)
{
if (property.isList)
{
size_t listSize = 0;
size_t dummyCount = 0;
read(property.listType, &listSize, dummyCount, is);
for (size_t i = 0; i < listSize; ++i)
{
read(property.propertyType, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset, is);
}
}
else
{
read(property.propertyType, (helper.data->buffer.get() + helper.cursor->byteOffset), helper.cursor->byteOffset, is);
}
}
else
{
helper.cursor->totalSizeBytes += skip(property, is);
}
}
else
{
skip(property, is);
}
}
}
}
// Reset istream reader to the beginning
if (firstPass) is.seekg(start, is.beg);
}
///////////////////////////////////
// Pass-Through Public Interface //
///////////////////////////////////
PlyFile::PlyFile() { impl.reset(new PlyFileImpl()); };
PlyFile::~PlyFile() { };
bool PlyFile::parse_header(std::istream & is) { return impl->parse_header(is); }
void PlyFile::read(std::istream & is) { return impl->read(is); }
void PlyFile::write(std::ostream & os, bool isBinary) { return impl->write(os, isBinary); }
std::vector<PlyElement> PlyFile::get_elements() const { return impl->elements; }
std::vector<std::string> & PlyFile::get_comments() { return impl->comments; }
std::vector<std::string> PlyFile::get_info() const { return impl->objInfo; }
std::shared_ptr<PlyData> PlyFile::request_properties_from_element(const std::string & elementKey, const std::initializer_list<std::string> propertyKeys)
{
return impl->request_properties_from_element(elementKey, propertyKeys);
}
void PlyFile::add_properties_to_element(const std::string & elementKey, const std::initializer_list<std::string> propertyKeys, const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount)
{
return impl->add_properties_to_element(elementKey, propertyKeys, type, count, data, listType, listCount);
}

Wyświetl plik

@ -1,119 +0,0 @@
// This software is in the public domain. Where that dedication is not
// recognized, you are granted a perpetual, irrevocable license to copy,
// distribute, and modify this file as you see fit.
// Authored in 2015 by Dimitri Diakopoulos (http://www.dimitridiakopoulos.com)
// https://github.com/ddiakopoulos/tinyply
// Version 2.0
#ifndef tinyply_h
#define tinyply_h
#include <vector>
#include <string>
#include <stdint.h>
#include <sstream>
#include <memory>
#include <map>
namespace tinyply
{
enum class Type : uint8_t
{
INVALID,
INT8,
UINT8,
INT16,
UINT16,
INT32,
UINT32,
FLOAT32,
FLOAT64
};
struct PropertyInfo
{
int stride;
std::string str;
};
static std::map<Type, PropertyInfo> PropertyTable
{
{ Type::INT8,{ 1, "char" } },
{ Type::UINT8,{ 1, "uchar" } },
{ Type::INT16,{ 2, "short" } },
{ Type::UINT16,{ 2, "ushort" } },
{ Type::INT32,{ 4, "int" } },
{ Type::UINT32,{ 4, "uint" } },
{ Type::FLOAT32,{ 4, "float" } },
{ Type::FLOAT64,{ 8, "double" } },
{ Type::INVALID,{ 0, "INVALID" } }
};
class Buffer
{
uint8_t * alias{ nullptr };
struct delete_array { void operator()(uint8_t * p) { delete[] p; } };
std::unique_ptr<uint8_t, decltype(Buffer::delete_array())> data;
size_t size;
public:
Buffer() {};
Buffer(const size_t size) : data(new uint8_t[size], delete_array()), size(size) { alias = data.get(); } // allocating
Buffer(uint8_t * ptr) { alias = ptr; } // non-allocating, fixme: set size?
uint8_t * get() { return alias; }
size_t size_bytes() const { return size; }
};
struct PlyData
{
Type t;
size_t count;
Buffer buffer;
};
struct PlyProperty
{
PlyProperty(std::istream & is);
PlyProperty(Type type, std::string & _name) : name(_name), propertyType(type) {}
PlyProperty(Type list_type, Type prop_type, std::string & _name, int list_count) : name(_name), propertyType(prop_type), isList(true), listType(list_type), listCount(list_count) {}
std::string name;
Type propertyType;
bool isList{ false };
Type listType{ Type::INVALID };
int listCount{ 0 };
};
struct PlyElement
{
PlyElement(std::istream & istream);
PlyElement(const std::string & _name, size_t count) : name(_name), size(count) {}
std::string name;
size_t size;
std::vector<PlyProperty> properties;
};
struct PlyFile
{
struct PlyFileImpl;
std::unique_ptr<PlyFileImpl> impl;
PlyFile();
~PlyFile();
bool parse_header(std::istream & is);
void read(std::istream & is);
void write(std::ostream & os, bool isBinary);
std::vector<PlyElement> get_elements() const;
std::vector<std::string> & get_comments();
std::vector<std::string> get_info() const;
std::shared_ptr<PlyData> request_properties_from_element(const std::string & elementKey, const std::initializer_list<std::string> propertyKeys);
void add_properties_to_element(const std::string & elementKey, const std::initializer_list<std::string> propertyKeys, const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount);
};
} // namesapce tinyply
#endif // tinyply_h

Wyświetl plik

@ -1,16 +1,13 @@
project(odm_25dmeshing)
project(odm_cleanmesh)
cmake_minimum_required(VERSION 2.8)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR})
set (CMAKE_CXX_STANDARD 11)
#set(VTK_DIR "/data/packages/VTK-7.1.1-build")
set(VTK_SMP_IMPLEMENTATION_TYPE TBB)
find_package(VTK 7.1.1 REQUIRED)
find_package(VTK REQUIRED)
include(${VTK_USE_FILE})
# Add compiler options.
#add_definitions(-Wall -Wextra -O0 -g3)
add_definitions(-Wall -Wextra)
# Add source directory
@ -19,5 +16,4 @@ aux_source_directory("./src" SRC_LIST)
# Add exectuteable
add_executable(${PROJECT_NAME} ${SRC_LIST})
target_link_libraries(odm_25dmeshing ${VTK_LIBRARIES})
target_link_libraries(${PROJECT_NAME} ${VTK_LIBRARIES})

Wyświetl plik

@ -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 <stdarg.h>
#include <cstring>
#include <cstdlib>
#include <string>
#include <vector>
#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

Wyświetl plik

@ -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 <cassert>
#include <string.h>
#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<Dim ; i++ ) values[i] = cmdLineCopy< Type >( v[i] );
else for( int i=0 ; i<Dim ; i++ ) values[i] = cmdLineInitialize< Type >();
}
template< class Type , int Dim >
cmdLineParameterArray< Type , Dim >::~cmdLineParameterArray( void ){ for( int i=0 ; i<Dim ; i++ ) cmdLineCleanUp< Type >( values+i ); }
template< class Type , int Dim >
int cmdLineParameterArray< Type , Dim >::read( char** argv , int argc )
{
if( argc>=Dim )
{
for( int i=0 ; i<Dim ; i++ ) cmdLineCleanUp< Type >( 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<Dim ; i++ )
{
cmdLineWriteValue< Type >( 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<count ; i++ ) values[i] = cmdLineStringToType< Type >( 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<count ; i++ )
{
cmdLineWriteValue< Type >( values[i] , temp );
temp = str+strlen( str );
}
}
inline char* FileExtension( char* fileName )
{
char* temp = fileName;
for( unsigned int i=0 ; i<strlen(fileName) ; i++ ) if( fileName[i]=='.' ) temp = &fileName[i+1];
return temp;
}
inline char* GetFileExtension( const char* fileName )
{
char* fileNameCopy;
char* ext=NULL;
char* temp;
fileNameCopy=new char[strlen(fileName)+1];
assert(fileNameCopy);
strcpy(fileNameCopy,fileName);
temp=strtok(fileNameCopy,".");
while(temp!=NULL)
{
if(ext!=NULL){delete[] ext;}
ext=new char[strlen(temp)+1];
assert(ext);
strcpy(ext,temp);
temp=strtok(NULL,".");
}
delete[] fileNameCopy;
return ext;
}
inline char* GetLocalFileName( const char* fileName )
{
char* fileNameCopy;
char* name=NULL;
char* temp;
fileNameCopy=new char[strlen(fileName)+1];
assert(fileNameCopy);
strcpy(fileNameCopy,fileName);
temp=strtok(fileNameCopy,"\\");
while(temp!=NULL){
if(name!=NULL){delete[] name;}
name=new char[strlen(temp)+1];
assert(name);
strcpy(name,temp);
temp=strtok(NULL,"\\");
}
delete[] fileNameCopy;
return name;
}
inline char* LocalFileName( char* fileName )
{
char* temp = fileName;
for( int i=0 ; i<(int)strlen(fileName) ; i++ ) if( fileName[i] =='\\' ) temp = &fileName[i+1];
return temp;
}
inline char* DirectoryName( char* fileName )
{
for( int i=int( strlen(fileName) )-1 ; 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 -<name>: %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<cnt;j++){delete[] names[j];}
delete[] names;
cnt=0;
fclose(fp);
return NULL;
}
strcpy(names[cnt],temp);
cnt++;
}
fclose(fp);
return names;
}

Wyświetl plik

@ -0,0 +1,33 @@
#include <cstdio>
#include <cstdarg>
#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 );
}
}
};

Wyświetl plik

@ -0,0 +1,114 @@
#include <iostream>
#include <string>
#include <fstream>
#include <vtkPolyDataConnectivityFilter.h>
#include <vtkSmartPointer.h>
#include <vtkPLYReader.h>
#include <vtkPLYWriter.h>
#include <vtkAlgorithmOutput.h>
#include <vtkQuadricDecimation.h>
#include "CmdLineParser.h"
#include "Logger.h"
Logger logWriter;
cmdLineParameter< char* >
InputFile( "inputFile" ) ,
OutputFile( "outputFile" );
cmdLineParameter< int >
DecimateMesh( "decimateMesh" );
cmdLineReadable
RemoveIslands( "removeIslands" ) ,
Verbose( "verbose" );
cmdLineReadable* params[] = {
&InputFile , &OutputFile , &DecimateMesh, &RemoveIslands, &Verbose ,
NULL
};
void help(char *ex){
std::cout << "Usage: " << ex << std::endl
<< "\t -" << InputFile.name << " <input polygon mesh>" << std::endl
<< "\t -" << OutputFile.name << " <output polygon mesh>" << std::endl
<< "\t [-" << DecimateMesh.name << " <target number of vertices>]" << std::endl
<< "\t [-" << RemoveIslands.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( !RemoveIslands.set && !DecimateMesh.set ) help (argv[0]);
logWriter.verbose = Verbose.set;
logWriter.outputFile = "odm_cleanmesh_log.txt";
logArgs(params, logWriter);
vtkSmartPointer<vtkPLYReader> reader =
vtkSmartPointer<vtkPLYReader>::New();
reader->SetFileName ( InputFile.value );
reader->Update();
vtkPolyData *nextOutput = reader->GetOutput();
vtkSmartPointer<vtkPolyDataConnectivityFilter> connectivityFilter =
vtkSmartPointer<vtkPolyDataConnectivityFilter>::New();
connectivityFilter->SetExtractionModeToLargestRegion();
vtkSmartPointer<vtkQuadricDecimation> decimationFilter =
vtkSmartPointer<vtkQuadricDecimation>::New();
if (RemoveIslands.set){
logWriter("Removing islands\n");
connectivityFilter->SetInputData(nextOutput);
connectivityFilter->Update();
nextOutput = connectivityFilter->GetOutput();
}
if (DecimateMesh.set){
logWriter("Decimating mesh\n");
int vertexCount = nextOutput->GetNumberOfPoints();
logWriter("Current vertex count: %d\n", vertexCount);
logWriter("Wanted vertex count: %d\n", DecimateMesh.value);
if (vertexCount > DecimateMesh.value){
double targetReduction = 1.0 - static_cast<double>(DecimateMesh.value) / static_cast<double>(vertexCount);
logWriter("Target reduction set to %f\n", targetReduction);
decimationFilter->SetTargetReduction(targetReduction);
decimationFilter->SetInputData(nextOutput);
decimationFilter->Update();
nextOutput = decimationFilter->GetOutput();
}else{
logWriter("Skipping decimation\n");
}
}
logWriter("Saving cleaned mesh to file... \n");
vtkSmartPointer<vtkPLYWriter> plyWriter =
vtkSmartPointer<vtkPLYWriter>::New();
plyWriter->SetFileName(OutputFile.value);
plyWriter->SetFileTypeToBinary();
plyWriter->SetInputData(nextOutput);
plyWriter->Write();
logWriter("OK\n");
}

Wyświetl plik

@ -148,11 +148,11 @@ def config():
'processes. Peak memory requirement is ~1GB per '
'thread and 2 megapixel image resolution. Default: %(default)s'))
parser.add_argument('--opensfm-depthmap-resolution',
parser.add_argument('--depthmap-resolution',
metavar='<positive float>',
type=float,
default=640,
help=('Resolution of the depthmaps. Higher values take longer to compute '
help=('Controls the density of the point cloud by setting the resolution of the depthmap images. Higher values take longer to compute '
'but produce denser point clouds. '
'Default: %(default)s'))
@ -188,10 +188,15 @@ def config():
help='Run local bundle adjustment for every image added to the reconstruction and a global '
'adjustment every 100 images. Speeds up reconstruction for very large datasets.')
parser.add_argument('--use-25dmesh',
parser.add_argument('--use-3dmesh',
action='store_true',
default=False,
help='Use a 2.5D mesh to compute the orthophoto. This option tends to provide better results for planar surfaces. Experimental.')
help='Use a full 3D mesh to compute the orthophoto instead of a 2.5D mesh. This option is a bit faster and provides similar results in planar areas.')
parser.add_argument('--skip-3dmodel',
action='store_true',
default=False,
help='Skip generation of a full 3D model. This can save time if you only need 2D results such as orthophotos and DEMs.')
parser.add_argument('--use-opensfm-dense',
action='store_true',
@ -205,14 +210,6 @@ def config():
help='Regularization parameter, a higher alpha leads to '
'smoother surfaces. Default: %(default)s')
parser.add_argument('--smvs-scale',
metavar='<non-negative integer>',
default=1,
type=int,
help='Scales the input images, which affects the output'
' density. 0 is original scale but takes longer '
'to process. 2 is 1/4 scale. Default: %(default)s')
parser.add_argument('--smvs-output-scale',
metavar='<positive integer>',
default=2,
@ -408,7 +405,7 @@ def config():
parser.add_argument('--dem-gapfill-steps',
metavar='<positive integer>',
default=4,
default=3,
type=int,
help='Number of steps used to fill areas with gaps. Set to 0 to disable gap filling. '
'Starting with a radius equal to the output resolution, N different DEMs are generated with '
@ -553,11 +550,16 @@ def config():
sys.exit(1)
if args.fast_orthophoto:
log.ODM_INFO('Fast orthophoto is turned on, automatically setting --use-25dmesh')
args.use_25dmesh = True
log.ODM_INFO('Fast orthophoto is turned on, automatically setting --skip-3dmodel and --use-opensfm-dense')
args.skip_3dmodel = True
args.use_opensfm_dense = True
if args.dtm and args.pc_classify == 'none':
log.ODM_INFO("DTM is turned on, automatically turning on point cloud classification")
args.pc_classify = "smrf"
if args.skip_3dmodel and args.use_3dmesh:
log.ODM_WARNING('--skip-3dmodel is set, but so is --use-3dmesh. You can\'t have both!')
sys.exit(1)
return args

Wyświetl plik

@ -63,7 +63,7 @@ class Cropper:
return geotiff_path
def create_bounds_geojson(self, pointcloud_path, buffer_distance = 0):
def create_bounds_geojson(self, pointcloud_path, buffer_distance = 0, decimation_step=40, outlier_radius=20):
"""
Compute a buffered polygon around the data extents (not just a bounding box)
of the given point cloud.
@ -80,11 +80,11 @@ class Cropper:
run("pdal translate -i \"{}\" "
"-o \"{}\" "
"decimation outlier range "
"--filters.decimation.step=40 "
"--filters.decimation.step={} "
"--filters.outlier.method=radius "
"--filters.outlier.radius=20 "
"--filters.outlier.radius={} "
"--filters.outlier.min_k=2 "
"--filters.range.limits='Classification![7:7]'".format(pointcloud_path, filtered_pointcloud_path))
"--filters.range.limits='Classification![7:7]'".format(pointcloud_path, filtered_pointcloud_path, decimation_step, outlier_radius))
if not os.path.exists(filtered_pointcloud_path):
log.ODM_WARNING('Could not filter point cloud, cannot generate shapefile bounds {}'.format(filtered_pointcloud_path))
@ -164,7 +164,7 @@ class Cropper:
return bounds_geojson_path
def create_bounds_shapefile(self, pointcloud_path, buffer_distance = 0):
def create_bounds_shapefile(self, pointcloud_path, buffer_distance = 0, decimation_step=40, outlier_radius=20):
"""
Compute a buffered polygon around the data extents (not just a bounding box)
of the given point cloud.
@ -175,7 +175,7 @@ class Cropper:
log.ODM_WARNING('Point cloud does not exist, cannot generate shapefile bounds {}'.format(pointcloud_path))
return ''
bounds_geojson_path = self.create_bounds_geojson(pointcloud_path, buffer_distance)
bounds_geojson_path = self.create_bounds_geojson(pointcloud_path, buffer_distance, decimation_step, outlier_radius)
summary_file_path = os.path.join(self.storage_dir, '{}.summary.json'.format(self.files_prefix))
run('pdal info --summary {0} > {1}'.format(pointcloud_path, summary_file_path))

Wyświetl plik

@ -1,8 +1,11 @@
import os, glob
import gippy
import numpy
from scipy import ndimage
from datetime import datetime
from opendm import log
from loky import get_reusable_executor
from functools import partial
from . import pdal
@ -23,13 +26,17 @@ def classify(lasFile, smrf=False, slope=1, cellsize=3, maxWindowSize=10, maxDist
def create_dems(filenames, demtype, radius=['0.56'], gapfill=False,
outdir='', suffix='', resolution=0.1, **kwargs):
outdir='', suffix='', resolution=0.1, max_workers=None, **kwargs):
""" Create DEMS for multiple radii, and optionally gapfill """
fouts = []
for rad in radius:
fouts.append(
create_dem(filenames, demtype,
radius=rad, outdir=outdir, suffix=suffix, resolution=resolution, **kwargs))
create_dem_for_radius = partial(create_dem,
filenames, demtype,
outdir=outdir, suffix=suffix, resolution=resolution, **kwargs)
with get_reusable_executor(max_workers=max_workers, timeout=None) as e:
fouts = list(e.map(create_dem_for_radius, radius))
fnames = {}
# convert from list of dicts, to dict of lists
for product in fouts[0].keys():
@ -52,7 +59,7 @@ def create_dems(filenames, demtype, radius=['0.56'], gapfill=False,
return _fouts
def create_dem(filenames, demtype, radius='0.56', decimation=None,
def create_dem(filenames, demtype, radius, decimation=None,
maxsd=None, maxz=None, maxangle=None, returnnum=None,
products=['idw'], outdir='', suffix='', verbose=False, resolution=0.1):
""" Create DEM from collection of LAS files """
@ -67,7 +74,10 @@ def create_dem(filenames, demtype, radius='0.56', decimation=None,
log.ODM_INFO('Creating %s from %s files' % (prettyname, len(filenames)))
# JSON pipeline
json = pdal.json_gdal_base(bname, products, radius, resolution)
json = pdal.json_add_filters(json, maxsd, maxz, maxangle, returnnum)
# A DSM for meshing does not use additional filters
if demtype != 'mesh_dsm':
json = pdal.json_add_filters(json, maxsd, maxz, maxangle, returnnum)
if demtype == 'dsm':
json = pdal.json_add_classification_filter(json, 2, equality='max')
@ -80,6 +90,7 @@ def create_dem(filenames, demtype, radius='0.56', decimation=None,
pdal.json_add_readers(json, filenames)
pdal.run_pipeline(json, verbose=verbose)
# verify existence of fout
exists = True
for f in fouts.values():
@ -92,13 +103,14 @@ def create_dem(filenames, demtype, radius='0.56', decimation=None,
return fouts
def gap_fill(filenames, fout, interpolation='nearest'):
def gap_fill(filenames, fout):
""" Gap fill from higher radius DTMs, then fill remainder with interpolation """
start = datetime.now()
from scipy.interpolate import griddata
if len(filenames) == 0:
raise Exception('No filenames provided!')
log.ODM_INFO('Starting gap-filling with nearest interpolation...')
filenames = sorted(filenames)
imgs = map(gippy.GeoImage, filenames)
@ -109,11 +121,16 @@ def gap_fill(filenames, fout, interpolation='nearest'):
locs = numpy.where(arr == nodata)
arr[locs] = imgs[i][0].read()[locs]
# interpolation at bad points
goodlocs = numpy.where(arr != nodata)
badlocs = numpy.where(arr == nodata)
arr[badlocs] = griddata(goodlocs, arr[goodlocs], badlocs, method=interpolation)
# Nearest neighbor interpolation at bad points
indices = ndimage.distance_transform_edt(arr == nodata,
return_distances=False,
return_indices=True)
arr = arr[tuple(indices)]
# Median filter
from scipy import signal
arr = signal.medfilt(arr, 5)
# write output
imgout = gippy.GeoImage.create_from(imgs[0], fout)
imgout.set_nodata(nodata)

Wyświetl plik

@ -170,10 +170,19 @@ def json_add_crop_filter(json, wkt):
return json
def is_ply_file(filename):
_, ext = os.path.splitext(filename)
return ext.lower() == '.ply'
def json_add_reader(json, filename):
""" Add LAS Reader Element and return """
""" Add Reader Element and return """
reader_type = 'readers.las' # default
if is_ply_file(filename):
reader_type = 'readers.ply'
json['pipeline'].insert(0, {
'type': 'readers.las',
'type': reader_type,
'filename': os.path.abspath(filename)
})
return json

157
opendm/mesh.py 100644
Wyświetl plik

@ -0,0 +1,157 @@
from __future__ import absolute_import
import os, shutil, sys, struct, random
from io import BytesIO
from gippy import GeoImage
from opendm.dem import commands
from opendm import system
from opendm import log
from opendm import context
from scipy import signal, ndimage
import numpy as np
def create_25dmesh(inPointCloud, outMesh, dsm_resolution=0.05, depth=8, samples=1, maxVertexCount=100000, verbose=False, max_workers=None):
# Create DSM from point cloud
# Create temporary directory
mesh_directory = os.path.dirname(outMesh)
tmp_directory = os.path.join(mesh_directory, 'tmp')
if os.path.exists(tmp_directory):
shutil.rmtree(tmp_directory)
os.mkdir(tmp_directory)
log.ODM_INFO('Created temporary directory: %s' % tmp_directory)
radius_steps = [dsm_resolution / 4.0, dsm_resolution / 2.0, dsm_resolution]
log.ODM_INFO('Creating DSM for 2.5D mesh')
commands.create_dems(
[inPointCloud],
'mesh_dsm',
radius=map(str, radius_steps),
gapfill=True,
outdir=tmp_directory,
resolution=dsm_resolution,
products=['idw'],
verbose=verbose,
max_workers=max_workers
)
dsm_points = dem_to_points(os.path.join(tmp_directory, 'mesh_dsm.tif'), os.path.join(tmp_directory, 'dsm_points.ply'))
mesh = screened_poisson_reconstruction(dsm_points, outMesh, depth=depth, samples=samples, maxVertexCount=maxVertexCount, verbose=verbose)
# Cleanup tmp
if os.path.exists(tmp_directory):
shutil.rmtree(tmp_directory)
return mesh
def dem_to_points(inGeotiff, outPointCloud):
log.ODM_INFO('Sampling points from DSM: %s' % inGeotiff)
image = GeoImage.open([inGeotiff], bandnames=['z'], nodata=-9999)
arr = image['z'].read_raw()
resolution = max(abs(image.resolution().x()), abs(image.resolution().y()))
log.ODM_INFO('Writing points...')
mem_file = BytesIO()
xmin, xmax, ymin, ymax = image.extent().x0(), image.extent().x1(), image.extent().y0(), image.extent().y1()
ext_width, ext_height = xmax - xmin, ymax - ymin
arr_height, arr_width = arr.shape
vertex_count = (arr_height - 4) * (arr_width - 4)
skirt_points = 0
skirt_height_threshold = 1 # meter
skirt_increments = 0.1
for x in range(2, arr_width - 2):
for y in range(2, arr_height - 2):
z = arr[y][x]
tx = xmin + (float(x) / float(arr_width)) * ext_width
ty = ymax - (float(y) / float(arr_height)) * ext_height
mem_file.write(struct.pack('ffffff', tx, ty, z, 0, 0, 1))
# Skirting
for (nx, ny) in ((x, y + 1), (x, y - 1), (x + 1, y), (x - 1, y)):
current_z = z
neighbor_z = arr[ny][nx]
if current_z - neighbor_z > skirt_height_threshold:
while current_z > neighbor_z:
current_z -= skirt_increments
mem_file.write(struct.pack('ffffff', tx, ty, current_z, 0, 0, 1))
skirt_points += 1
mem_file.write(struct.pack('ffffff', tx, ty, neighbor_z, 0, 0, 1))
skirt_points += 1
with open(outPointCloud, "wb") as f:
f.write("ply\n")
f.write("format binary_%s_endian 1.0\n" % sys.byteorder)
f.write("element vertex %s\n" % (vertex_count + skirt_points))
f.write("property float x\n")
f.write("property float y\n")
f.write("property float z\n")
f.write("property float nx\n")
f.write("property float ny\n")
f.write("property float nz\n")
f.write("end_header\n")
f.write(mem_file.getvalue())
mem_file.close()
log.ODM_INFO("Points count: %s (%s samples, %s skirts)", vertex_count + skirt_points, vertex_count, skirt_points)
log.ODM_INFO('Wrote points to: %s' % outPointCloud)
return outPointCloud
def screened_poisson_reconstruction(inPointCloud, outMesh, depth = 8, samples = 1, maxVertexCount=100000, pointWeight=4, threads=context.num_cores, verbose=False):
mesh_path, mesh_filename = os.path.split(outMesh)
# mesh_path = path/to
# mesh_filename = odm_mesh.ply
basename, ext = os.path.splitext(mesh_filename)
# basename = odm_mesh
# ext = .ply
outMeshDirty = os.path.join(mesh_path, "{}.dirty{}".format(basename, ext))
poissonReconArgs = {
'bin': context.poisson_recon_path,
'outfile': outMeshDirty,
'infile': inPointCloud,
'depth': depth,
'samples': samples,
'pointWeight': pointWeight,
'verbose': '--verbose' if verbose else ''
}
# Run PoissonRecon (new)
system.run('{bin} --in {infile} '
'--out {outfile} '
'--depth {depth} '
'--linearFit '
'{verbose}'.format(**poissonReconArgs))
# Cleanup and reduce vertex count if necessary
cleanupArgs = {
'bin': context.odm_modules_path,
'outfile': outMesh,
'infile': outMeshDirty,
'max_vertex': maxVertexCount,
'verbose': '-verbose' if verbose else ''
}
system.run('{bin}/odm_cleanmesh -inputFile {infile} '
'-outputFile {outfile} '
'-removeIslands '
'-decimateMesh {max_vertex} {verbose} '.format(**cleanupArgs))
# Delete intermediate results
os.remove(outMeshDirty)
return outMesh

Wyświetl plik

@ -1,118 +0,0 @@
import log
import system
import dataset
import types
from scripts.opensfm import opensfm
# Define pipeline tasks
tasks_dict = {'1': 'opensfm',
'4': 'odm_meshing',
'5': 'mvs_texturing',
'6': 'odm_georeferencing',
'7': 'odm_dem',
'8': 'odm_orthophoto',
'9': 'zip_results'}
class ODMTaskManager(object):
"""docstring for ODMTaskManager"""
def __init__(self, odm_app):
self.odm_app = odm_app
self.initial_task_id = 0
self.current_task_id = 0
self.final_task_id = len(tasks_dict)
self.tasks = self.init_tasks(tasks_dict, self.odm_app)
def init_tasks(self, _tasks_dict, _odm_app):
# dict to store tasks objects
tasks = {}
# loop over tasks dict
for key, in _tasks_dict:
# instantiate and append ODMTask
task_name = _tasks_dict[key]
tasks[key] = ODMTask(key, task_name)
# setup tasks
if task_name == 'resize':
# setup this task
command = resize
inputs = {'project_path': _odm_app.project_path,
'args': _odm_app.args,
'photos': _odm_app.photos}
elif task_name == 'opensfm':
# setup this task
command = opensfm
inputs = {'project_path': _odm_app.project_path,
'args': _odm_app.args,
'photos': _odm_app.photos}
elif task_name in [ 'odm_meshing', 'mvs_texturing', 'odm_georeferencing', 'odm_orthophoto', 'zip_results']:
# setup this task
command = None
inputs = {}
else:
log.ODM_ERROR('task_name %s is not valid' % task_name)
# setup task configuration
task = tasks[key]
task.command = command
task.inputs = inputs
return tasks
def run_tasks(self):
# curr_task = self.tasks['resize']
# self.tasks['resize']
for id in range(self.initial_task_id, self.final_task_id + 1):
# catch task with current id
task = self.tasks[str(id)]
# update task tracking
log.ODM_INFO('Running task %s: %s' % (task.id, task.name))
self.current_task_id = task.id
# run task
task.state = task.run()
if task.state == 2:
log.ODM_INFO('Succeeded task %s: %s - %s' % (task.id, task.name, system.now()))
else:
log.ODM_ERROR('Aborted task %s: %s' % (task.id, task.name))
class ODMTask(object):
"""docstring for ODMTask"""
def __init__(self, id, name):
# task definition
self.id = id
self.name = name
# task i/o
self.command = None
self.inputs = {}
# Current task state (0:waiting, 1:running, 2:succeded: 3:failed)
# By default we set a task in waiting state
self.state = 0
# Launch task
def run(self):
# while doing something
self.state = 1
return self.launch_command()
def launch_command(self):
if self.command is None:
log.ODM_ERROR('Call method for task %s not defined' % self.name)
return 3 # failed
# run conmmand
try:
succeed = self.command(**self.inputs)
return 2 if succeed else 3 # 2:succeed, 3:failed
except Exception, e:
log.ODM_ERROR(str(e))
return 3 # failed

Wyświetl plik

@ -59,7 +59,7 @@ class ODMLoadDatasetCell(ecto.Cell):
# define paths and create working directories
system.mkdir_p(tree.odm_georeferencing)
if args.use_25dmesh: system.mkdir_p(tree.odm_25dgeoreferencing)
if not args.use_3dmesh: system.mkdir_p(tree.odm_25dgeoreferencing)
log.ODM_DEBUG('Loading dataset from: %s' % images_dir)

Wyświetl plik

@ -36,7 +36,7 @@ class ODMMvsTexCell(ecto.Cell):
# define paths and create working directories
system.mkdir_p(tree.odm_texturing)
if args.use_25dmesh: system.mkdir_p(tree.odm_25dtexturing)
if not args.use_3dmesh: system.mkdir_p(tree.odm_25dtexturing)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
@ -48,23 +48,17 @@ class ODMMvsTexCell(ecto.Cell):
runs = [{
'out_dir': tree.odm_texturing,
'model': tree.odm_mesh,
'force_skip_vis_test': False
'nadir': False
}]
if args.fast_orthophoto:
if args.skip_3dmodel:
runs = []
if args.use_25dmesh:
if not args.use_3dmesh:
runs += [{
'out_dir': tree.odm_25dtexturing,
'model': tree.odm_25dmesh,
# We always skip the visibility test when using the 2.5D mesh
# because many faces end up being narrow, and almost perpendicular
# to the ground plane. The visibility test improperly classifies
# them as "not seen" since the test is done on a single triangle vertex,
# and while one vertex might be occluded, the other two might not.
'force_skip_vis_test': True
'nadir': True
}]
for r in runs:
@ -80,8 +74,9 @@ class ODMMvsTexCell(ecto.Cell):
skipLocalSeamLeveling = ""
skipHoleFilling = ""
keepUnseenFaces = ""
nadir = ""
if (self.params.skip_vis_test or r['force_skip_vis_test']):
if (self.params.skip_vis_test):
skipGeometricVisibilityTest = "--skip_geometric_visibility_test"
if (self.params.skip_glob_seam_leveling):
skipGlobalSeamLeveling = "--skip_global_seam_leveling"
@ -91,6 +86,8 @@ class ODMMvsTexCell(ecto.Cell):
skipHoleFilling = "--skip_hole_filling"
if (self.params.keep_unseen_faces):
keepUnseenFaces = "--keep_unseen_faces"
if (r['nadir']):
nadir = '--nadir_mode'
# mvstex definitions
kwargs = {
@ -104,7 +101,8 @@ class ODMMvsTexCell(ecto.Cell):
'skipLocalSeamLeveling': skipLocalSeamLeveling,
'skipHoleFilling': skipHoleFilling,
'keepUnseenFaces': keepUnseenFaces,
'toneMapping': self.params.tone_mapping
'toneMapping': self.params.tone_mapping,
'nadirMode': nadir
}
if args.use_opensfm_dense:
@ -127,7 +125,8 @@ class ODMMvsTexCell(ecto.Cell):
'{skipGlobalSeamLeveling} '
'{skipLocalSeamLeveling} '
'{skipHoleFilling} '
'{keepUnseenFaces}'.format(**kwargs))
'{keepUnseenFaces} '
'{nadirMode}'.format(**kwargs))
else:
log.ODM_WARNING('Found a valid ODM Texture file in: %s'
% odm_textured_model_obj)

Wyświetl plik

@ -50,7 +50,7 @@ class ODMApp(ecto.BlackBox):
hybrid_bundle_adjustment=p.args.use_hybrid_bundle_adjustment),
'slam': ODMSlamCell(),
'smvs': ODMSmvsCell(alpha=p.args.smvs_alpha,
scale=p.args.smvs_scale,
max_pixels=p.args.depthmap_resolution*p.args.depthmap_resolution,
threads=p.args.max_concurrency,
output_scale=p.args.smvs_output_scale,
shading=p.args.smvs_enable_shading,

Wyświetl plik

@ -91,9 +91,9 @@ class ODMDEMCell(ecto.Cell):
if args.dsm: products.append('dsm')
if args.dtm: products.append('dtm')
radius_steps = [args.dem_resolution]
radius_steps = [args.dem_resolution / 4.0]
for _ in range(args.dem_gapfill_steps - 1):
radius_steps.append(radius_steps[-1] * 3) # 3 is arbitrary, maybe there's a better value?
radius_steps.append(radius_steps[-1] * 2) # 2 is arbitrary, maybe there's a better value?
for product in products:
commands.create_dems(
@ -106,7 +106,8 @@ class ODMDEMCell(ecto.Cell):
maxsd=args.dem_maxsd,
maxangle=args.dem_maxangle,
decimation=args.dem_decimation,
verbose=args.verbose
verbose=args.verbose,
max_workers=args.max_concurrency
)
if args.crop > 0:

Wyświetl plik

@ -39,6 +39,7 @@ class ODMGeoreferencingCell(ecto.Cell):
reconstruction = inputs.reconstruction
gcpfile = tree.odm_georeferencing_gcp
doPointCloudGeo = True
transformPointCloud = True
verbose = '-verbose' if self.params.verbose else ''
# check if we rerun cell or not
@ -54,10 +55,10 @@ class ODMGeoreferencingCell(ecto.Cell):
'model': os.path.join(tree.odm_texturing, tree.odm_textured_model_obj)
}]
if args.fast_orthophoto:
if args.skip_3dmodel:
runs = []
if args.use_25dmesh:
if not args.use_3dmesh:
runs += [{
'georeferencing_dir': tree.odm_25dgeoreferencing,
'texturing_dir': tree.odm_25dtexturing,
@ -100,6 +101,11 @@ class ODMGeoreferencingCell(ecto.Cell):
else:
kwargs['pc'] = tree.smvs_model
if transformPointCloud:
kwargs['pc_params'] = '-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo}'.format(**kwargs)
else:
kwargs['pc_params'] = ''
# Check to see if the GCP file exists
if not self.params.use_exif and (self.params.gcp_file or tree.odm_georeferencing_gcp):
@ -107,7 +113,7 @@ class ODMGeoreferencingCell(ecto.Cell):
try:
system.run('{bin}/odm_georef -bundleFile {bundle} -imagesPath {imgs} -imagesListPath {imgs_list} '
'-inputFile {model} -outputFile {model_geo} '
'-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} '
'{pc_params} {verbose} '
'-logFile {log} -outputTransformFile {transform_file} -georefFileOutputPath {geo_sys} -gcpFile {gcp} '
'-outputCoordFile {coords}'.format(**kwargs))
except Exception:
@ -117,13 +123,13 @@ class ODMGeoreferencingCell(ecto.Cell):
log.ODM_INFO('Running georeferencing with OpenSfM transformation matrix')
system.run('{bin}/odm_georef -bundleFile {bundle} -inputTransformFile {input_trans_file} -inputCoordFile {coords} '
'-inputFile {model} -outputFile {model_geo} '
'-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} '
'{pc_params} {verbose} '
'-logFile {log} -outputTransformFile {transform_file} -georefFileOutputPath {geo_sys}'.format(**kwargs))
elif io.file_exists(tree.odm_georeferencing_coords):
log.ODM_INFO('Running georeferencing with generated coords file.')
system.run('{bin}/odm_georef -bundleFile {bundle} -inputCoordFile {coords} '
'-inputFile {model} -outputFile {model_geo} '
'-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo} {verbose} '
'{pc_params} {verbose} '
'-logFile {log} -outputTransformFile {transform_file} -georefFileOutputPath {geo_sys}'.format(**kwargs))
else:
log.ODM_WARNING('Georeferencing failed. Make sure your '
@ -171,12 +177,15 @@ class ODMGeoreferencingCell(ecto.Cell):
if args.crop > 0:
log.ODM_INFO("Calculating cropping area and generating bounds shapefile from point cloud")
cropper = Cropper(tree.odm_georeferencing, 'odm_georeferenced_model')
cropper.create_bounds_shapefile(tree.odm_georeferencing_model_laz, args.crop)
cropper.create_bounds_shapefile(tree.odm_georeferencing_model_laz, args.crop,
decimation_step=40 if args.fast_orthophoto or args.use_opensfm_dense else 90,
outlier_radius=20 if args.fast_orthophoto else 2)
# Do not execute a second time, since
# We might be doing georeferencing for
# multiple models (3D, 2.5D, ...)
doPointCloudGeo = False
transformPointCloud = False
else:
log.ODM_WARNING('Found a valid georeferenced model in: %s'
% odm_georeferencing_model_ply_geo)

Wyświetl plik

@ -4,6 +4,7 @@ from opendm import log
from opendm import io
from opendm import system
from opendm import context
from opendm import mesh
class ODMeshingCell(ecto.Cell):
@ -54,52 +55,43 @@ class ODMeshingCell(ecto.Cell):
elif args.fast_orthophoto:
infile = os.path.join(tree.opensfm, 'reconstruction.ply')
# Do not create full 3D model with fast_orthophoto
if not args.fast_orthophoto:
# Create full 3D model unless --skip-3dmodel is set
if not args.skip_3dmodel:
if not io.file_exists(tree.odm_mesh) or rerun_cell:
log.ODM_DEBUG('Writing ODM Mesh file in: %s' % tree.odm_mesh)
kwargs = {
'bin': context.poisson_recon_path,
'outfile': tree.odm_mesh,
'infile': infile,
'oct_tree': self.params.oct_tree,
'point_weight': self.params.point_weight,
'threads': self.params.max_concurrency,
'samples': self.params.samples,
'verbose': verbose
}
mesh.screened_poisson_reconstruction(infile,
tree.odm_mesh,
depth=self.params.oct_tree,
samples=self.params.samples,
maxVertexCount=self.params.max_vertex,
pointWeight=self.params.point_weight,
threads=self.params.max_concurrency,
verbose=verbose)
system.run('{bin} --in {infile} --out {outfile} '
'--depth {oct_tree} --pointWeight {point_weight} '
'--linearFit --normals --density --samplesPerNode {samples} '
'--threads {threads} {verbose}'.format(**kwargs))
else:
log.ODM_WARNING('Found a valid ODM Mesh file in: %s' %
tree.odm_mesh)
# Do we need to generate a 2.5D mesh also?
# This is always set if fast_orthophoto is set
if args.use_25dmesh:
# Always generate a 2.5D mesh
# unless --use-3dmesh is set.
if not args.use_3dmesh:
if not io.file_exists(tree.odm_25dmesh) or rerun_cell:
log.ODM_DEBUG('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh)
dsm_resolution = 1.0 / float(args.orthophoto_resolution)
kwargs = {
'bin': context.odm_modules_path,
'outfile': tree.odm_25dmesh,
'infile': infile,
'log': tree.odm_25dmeshing_log,
'verbose': verbose,
'max_vertex': self.params.max_vertex,
'neighbors': args.mesh_neighbors,
'resolution': args.mesh_resolution
}
# Sparse point clouds benefits from using
# a larger resolution value (more radius interolation, less holes)
if args.fast_orthophoto:
dsm_resolution *= 2
# run 2.5D meshing binary
system.run('{bin}/odm_25dmeshing -inputFile {infile} '
'-outputFile {outfile} -logFile {log} '
'-maxVertexCount {max_vertex} -neighbors {neighbors} '
'-resolution {resolution} {verbose}'.format(**kwargs))
mesh.create_25dmesh(infile, tree.odm_25dmesh,
dsm_resolution=dsm_resolution,
depth=self.params.oct_tree,
maxVertexCount=self.params.max_vertex,
verbose=self.params.verbose,
max_workers=args.max_concurrency)
else:
log.ODM_WARNING('Found a valid ODM 2.5D Mesh file in: %s' %
tree.odm_25dmesh)

Wyświetl plik

@ -77,15 +77,15 @@ class ODMOrthoPhotoCell(ecto.Cell):
if georef:
if args.use_25dmesh:
kwargs['model_geo'] = os.path.join(tree.odm_25dtexturing, tree.odm_georeferencing_model_obj_geo)
else:
if args.use_3dmesh:
kwargs['model_geo'] = os.path.join(tree.odm_texturing, tree.odm_georeferencing_model_obj_geo)
else:
if args.use_25dmesh:
kwargs['model_geo'] = os.path.join(tree.odm_25dtexturing, tree.odm_textured_model_obj)
else:
kwargs['model_geo'] = os.path.join(tree.odm_25dtexturing, tree.odm_georeferencing_model_obj_geo)
else:
if args.use_3dmesh:
kwargs['model_geo'] = os.path.join(tree.odm_texturing, tree.odm_textured_model_obj)
else:
kwargs['model_geo'] = os.path.join(tree.odm_25dtexturing, tree.odm_textured_model_obj)
# run odm_orthophoto
system.run('{bin}/odm_orthophoto -inputFile {model_geo} '

Wyświetl plik

@ -76,7 +76,7 @@ class ODMOpenSfMCell(ecto.Cell):
"processes: %s" % self.params.processes,
"matching_gps_neighbors: %s" % self.params.matching_gps_neighbors,
"depthmap_method: %s" % args.opensfm_depthmap_method,
"depthmap_resolution: %s" % args.opensfm_depthmap_resolution,
"depthmap_resolution: %s" % args.depthmap_resolution,
"depthmap_min_patch_sd: %s" % args.opensfm_depthmap_min_patch_sd,
"depthmap_min_consistent_views: %s" % args.opensfm_depthmap_min_consistent_views,
"optimize_camera_parameters: %s" % ('no' if self.params.fixed_camera_params else 'yes')

Wyświetl plik

@ -1,4 +1,4 @@
import ecto
import ecto, shutil, os, glob
from opendm import log
from opendm import io
@ -10,7 +10,7 @@ class ODMSmvsCell(ecto.Cell):
def declare_params(self, params):
params.declare("threads", "max number of threads", context.num_cores)
params.declare("alpha", "Regularization parameter", 1)
params.declare("scale", "input scale", 1)
params.declare("max_pixels", "max pixels for reconstruction", 1700000)
params.declare("output_scale", "scale of optimization", 2)
params.declare("shading", "Enable shading-aware model", False)
params.declare("gamma_srgb", "Apply inverse SRGB gamma correction", False)
@ -39,8 +39,6 @@ class ODMSmvsCell(ecto.Cell):
log.ODM_ERROR('Not enough photos in photos array to start SMVS')
return ecto.QUIT
system.mkdir_p(tree.smvs)
# check if we rerun cell or not
rerun_cell = (args.rerun is not None and
args.rerun == 'smvs') or \
@ -58,6 +56,12 @@ class ODMSmvsCell(ecto.Cell):
io.copy(tree.opensfm_image_list, tree.mve_image_list)
io.copy(tree.opensfm_bundle, tree.mve_bundle)
# mve makescene wants the output directory
# to not exists before executing it (otherwise it
# will prompt the user for confirmation)
if io.dir_exists(tree.smvs):
shutil.rmtree(tree.smvs)
# run mve makescene
if not io.dir_exists(tree.mve_views):
system.run('%s %s %s' % (context.makescene_path, tree.mve_path, tree.smvs))
@ -66,7 +70,7 @@ class ODMSmvsCell(ecto.Cell):
config = [
"-t%s" % self.params.threads,
"-a%s" % self.params.alpha,
"-s%s" % self.params.scale,
"--max-pixels=%s" % self.params.max_pixels,
"-o%s" % self.params.output_scale,
"--debug-lvl=%s" % ('1' if self.params.verbose else '0'),
"%s" % '-S' if self.params.shading else '',
@ -76,13 +80,16 @@ class ODMSmvsCell(ecto.Cell):
# run smvs
system.run('%s %s %s' % (context.smvs_path, ' '.join(config), tree.smvs))
# rename the file for simplicity
old_file = io.join_paths(tree.smvs, 'smvs-%s%s.ply' %
('S' if self.params.shading else 'B', self.params.scale))
if not (io.rename_file(old_file, tree.smvs_model)):
log.ODM_WARNING("File %s does not exist, cannot be renamed. " % old_file)
# find and rename the output file for simplicity
smvs_files = glob.glob(os.path.join(tree.smvs, 'smvs-*'))
smvs_files.sort(key=os.path.getmtime) # sort by last modified date
if len(smvs_files) > 0:
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)
else:
log.ODM_WARNING("Cannot find a valid point cloud (smvs-XX.ply) in %s. Check the console output for errors." % tree.smvs)
else:
log.ODM_WARNING('Found a valid SMVS reconstruction file in: %s' %
tree.smvs_model)