//########################################################################## //# # //# CLOUDCOMPARE WRAPPER: PoissonReconLib # //# # //# 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; version 2 or later of the License. # //# # //# 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. # //# # //# COPYRIGHT: Daniel Girardeau-Montaut # //# # //########################################################################## #include "PoissonReconLib.h" //PoissonRecon #include "../Src/FEMTree.h" #include // The order of the B-Spline used to splat in data for color interpolation static const int DATA_DEGREE = 0; // The order of the B-Spline used to splat in the weights for density estimation static const int WEIGHT_DEGREE = 2; // The order of the B-Spline used to splat in the normals for constructing the Laplacian constraints static const int NORMAL_DEGREE = 2; // The default finite-element degree static const int DEFAULT_FEM_DEGREE = 1; // The dimension of the system static const int DIMENSION = 3; PoissonReconLib::Parameters::Parameters() : depth(8) //8 , cgDepth(0) //0 , kernelDepth(0) //? , adaptiveExp(1) //AdaptiveExponent (1) , iters(8) //8 , fullDepth(5) //5 , maxSolveDepth(0) //? , boundary(DIRICHLET) , threads(1) //ideally omp_get_num_procs() , samplesPerNode(1.5f) //1.5f , scale(1.1f) //1.1f , cgAccuracy(1.0e-3f) //1.0e-3f , pointWeight(4.0f) //4.0f , showResidual(false) , confidence(false) , nonManifold(false) , density(false) , colorInterp(16.0f) { #ifdef WITH_OPENMP threads = omp_get_num_procs(); #endif } template class PointData { public: PointData() : normal{ 0, 0, 0 }, color{ 0, 0, 0 } {} PointData(const Real _normal[3], const Real _color[3], Real scale = 1.0) { normal[0] = scale * _normal[0]; normal[1] = scale * _normal[1]; normal[2] = scale * _normal[2]; color[0] = scale * _color[0]; color[1] = scale * _color[1]; color[2] = scale * _color[2]; } PointData operator * (Real s) const { return PointData(normal, color, s); } PointData operator / (Real s) const { return PointData(normal, color, 1 / s); } PointData& operator += (const PointData& d) { normal[0] += d.normal[0]; normal[1] += d.normal[1]; normal[2] += d.normal[2]; color[0] += d.color[0]; color[1] += d.color[1]; color[2] += d.color[2]; return *this; } PointData& operator *= (Real s) { normal[0] *= s; normal[1] *= s; normal[2] *= s; color[0] *= s; color[1] *= s; color[2] *= s; return *this; } public: Real normal[3]; Real color[3]; }; template class Vertex : public PointData<_Real> { public: typedef _Real Real; Vertex(const Point& point) : PointData() , point(point) , w(0) {} Vertex(const Point& point, const PointData& data, double _w = 0.0) : PointData(data.normal, data.color) , point(point) , w(_w) {} Vertex() : Vertex(Point(0, 0, 0)) {} Vertex& operator *= (Real s) { PointData::operator *= (s); point *= s; w *= s; return *this; } Vertex& operator /= (Real s) { PointData::operator *= (1 / s); point /= s; w /= s; return *this; } Vertex& operator+=(const Vertex& p) { PointData::operator += (p); point += p.point; w += p.w; return *this; } public: Point point; double w; }; template class PointStream : public InputPointStreamWithData > { public: PointStream(const PoissonReconLib::ICloud& _cloud) : cloud(_cloud), xform(nullptr), currentIndex(0) {} void reset(void) override { currentIndex = 0; } bool nextPoint(Point& p, PointData& d) override { if (currentIndex >= cloud.size()) { return false; } cloud.getPoint(currentIndex, p.coords); if (xform != nullptr) { p = (*xform) * p; } if (cloud.hasNormals()) { cloud.getNormal(currentIndex, d.normal); } else { d.normal[0] = d.normal[1] = d.normal[2]; } if (cloud.hasColors()) { cloud.getColor(currentIndex, d.color); } else { d.color[0] = d.color[1] = d.color[2]; } currentIndex++; return true; } public: const PoissonReconLib::ICloud& cloud; XForm* xform; size_t currentIndex; }; template struct FEMTreeProfiler { FEMTree& tree; double t; FEMTreeProfiler(FEMTree& t) : tree(t) {} void start(void) { t = Time(), FEMTree::ResetLocalMemoryUsage(); } void dumpOutput(const char* header) const { FEMTree::MemoryUsage(); //if (header) { // utility::LogDebug("{} {} (s), {} (MB) / {} (MB) / {} (MB)", header, // Time() - t, // FEMTree::LocalMemoryUsage(), // FEMTree::MaxMemoryUsage(), // MemoryInfo::PeakMemoryUsageMB()); //} //else { // utility::LogDebug("{} (s), {} (MB) / {} (MB) / {} (MB)", Time() - t, // FEMTree::LocalMemoryUsage(), // FEMTree::MaxMemoryUsage(), // MemoryInfo::PeakMemoryUsageMB()); //} } }; template XForm GetBoundingBoxXForm(Point min, Point max, Real scaleFactor) { Point center = (max + min) / 2; Real scale = max[0] - min[0]; for (unsigned int d = 1; d < Dim; d++) { scale = std::max(scale, max[d] - min[d]); } scale *= scaleFactor; for (unsigned int i = 0; i < Dim; i++) { center[i] -= scale / 2; } XForm tXForm = XForm::Identity(), sXForm = XForm::Identity(); for (unsigned int i = 0; i < Dim; i++) { sXForm(i, i) = (Real)(1. / scale), tXForm(Dim, i) = -center[i]; } return sXForm * tXForm; } template XForm GetBoundingBoxXForm(Point min, Point max, Real width, Real scaleFactor, int& depth) { // Get the target resolution (along the largest dimension) Real resolution = (max[0] - min[0]) / width; for (unsigned int d = 1; d < Dim; d++) { resolution = std::max(resolution, (max[d] - min[d]) / width); } resolution *= scaleFactor; depth = 0; while ((1 << depth) < resolution) { depth++; } Point center = (max + min) / 2; Real scale = (1 << depth) * width; for (unsigned int i = 0; i < Dim; i++) { center[i] -= scale / 2; } XForm tXForm = XForm::Identity(), sXForm = XForm::Identity(); for (unsigned int i = 0; i < Dim; i++) { sXForm(i, i) = (Real)(1. / scale), tXForm(Dim, i) = -center[i]; } return sXForm * tXForm; } template XForm GetPointXForm(InputPointStream& stream, Real width, Real scaleFactor, int& depth) { Point min, max; stream.boundingBox(min, max); return GetBoundingBoxXForm(min, max, width, scaleFactor, depth); } template XForm GetPointXForm(InputPointStream& stream, Real scaleFactor) { Point min, max; stream.boundingBox(min, max); return GetBoundingBoxXForm(min, max, scaleFactor); } template struct ConstraintDual { Real target, weight; ConstraintDual(Real t, Real w) : target(t), weight(w) {} CumulativeDerivativeValues operator()( const Point& p) const { return CumulativeDerivativeValues(target * weight); }; }; template struct SystemDual { Real weight; SystemDual(Real w) : weight(w) {} CumulativeDerivativeValues operator()( const Point& p, const CumulativeDerivativeValues& dValues) const { return dValues * weight; }; CumulativeDerivativeValues operator()( const Point& p, const CumulativeDerivativeValues& dValues) const { return dValues * weight; }; }; template struct SystemDual { typedef double Real; Real weight; SystemDual(Real w) : weight(w) {} CumulativeDerivativeValues operator()( const Point& p, const CumulativeDerivativeValues& dValues) const { return dValues * weight; }; }; template void ExtractMesh( float datax, bool linear_fit, UIntPack, std::tuple, FEMTree& tree, const DenseNodeData>& solution, Real isoValue, const std::vector::PointSample>* samples, std::vector< PointData >* sampleData, const typename FEMTree::template DensityEstimator* density, const SetVertexFunction& SetVertex, XForm iXForm, PoissonReconLib::IMesh& out_mesh) { static const int Dim = sizeof...(FEMSigs); typedef UIntPack Sigs; static const unsigned int DataSig = FEMDegreeAndBType::Signature; typedef typename FEMTree::template DensityEstimator DensityEstimator; FEMTreeProfiler profiler(tree); CoredMeshData* mesh; mesh = new CoredVectorMeshData(); bool non_manifold = true; bool polygon_mesh = false; profiler.start(); typename IsoSurfaceExtractor::IsoStats isoStats; if (sampleData) { SparseNodeData, Real>, IsotropicUIntPack> _sampleData = tree.template setMultiDepthDataField( *samples, *sampleData, (DensityEstimator*)NULL); for (const RegularTreeNode* n = tree.tree().nextNode(); n; n = tree.tree().nextNode(n)) { ProjectiveData, Real>* clr = _sampleData(n); if (clr) (*clr) *= (Real)pow(datax, tree.depth(n)); } isoStats = IsoSurfaceExtractor::template Extract< PointData >(Sigs(), UIntPack(), UIntPack(), tree, density, &_sampleData, solution, isoValue, *mesh, SetVertex, !linear_fit, !non_manifold, polygon_mesh, false); } else { isoStats = IsoSurfaceExtractor::template Extract< PointData >(Sigs(), UIntPack(), UIntPack(), tree, density, NULL, solution, isoValue, *mesh, SetVertex, !linear_fit, !non_manifold, polygon_mesh, false); } mesh->resetIterator(); for (size_t vidx = 0; vidx < mesh->outOfCorePointCount(); ++vidx) { Vertex v; mesh->nextOutOfCorePoint(v); v.point = iXForm * v.point; out_mesh.addVertex(v.point.coords); out_mesh.addNormal(v.normal); out_mesh.addColor(v.color); out_mesh.addDensity(v.w); } for (size_t tidx = 0; tidx < mesh->polygonCount(); ++tidx) { std::vector> triangle; mesh->nextPolygon(triangle); if (triangle.size() == 3) { out_mesh.addTriangle(triangle[0].idx, triangle[1].idx, triangle[2].idx); } else { assert(false); } } delete mesh; } template static Real ComputeNorm(const Real vec[3]) { return sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]); } template static void Execute(PointStream& pointStream, PoissonReconLib::IMesh& out_mesh, int depth, Real width, float scale, bool linear_fit, UIntPack) { static const int Dim = sizeof...(FEMSigs); typedef UIntPack Sigs; typedef UIntPack::Degree...> Degrees; typedef UIntPack::BType, 1>::BType>::Signature...> NormalSigs; typedef typename FEMTree::template DensityEstimator DensityEstimator; typedef typename FEMTree::template InterpolationInfo InterpolationInfo; XForm xForm, iXForm; xForm = XForm::Identity(); float datax = 32.f; int base_depth = 0; int base_v_cycles = 1; float confidence = 0.f; float point_weight = 2.f * DEFAULT_FEM_DEGREE; float confidence_bias = 0.f; float samples_per_node = 1.5f; float cg_solver_accuracy = 1e-3f; int full_depth = 5; int iters = 8; bool exact_interpolation = false; double startTime = Time(); Real isoValue = 0; FEMTree tree(MEMORY_ALLOCATOR_BLOCK_SIZE); FEMTreeProfiler profiler(tree); size_t pointCount; Real pointWeightSum; std::vector::PointSample> samples; std::vector< PointData > sampleData; DensityEstimator* density = NULL; SparseNodeData, NormalSigs>* normalInfo = NULL; Real targetValue = (Real)0.5; // Read in the samples (and color data) { if (width > 0) { xForm = GetPointXForm(pointStream, width, static_cast(scale > 0 ? scale : 1.0), depth) * xForm; } else { xForm = scale > 0 ? GetPointXForm(pointStream, (Real)scale) * xForm : xForm; } pointStream.xform = &xForm; { auto ProcessDataWithConfidence = [&](const Point& p, PointData& d) { Real l = ComputeNorm(d.normal); if (!l || l != l) return (Real)-1.; return (Real)pow(l, confidence); }; auto ProcessData = [](const Point& p, PointData& d) { Real l = ComputeNorm(d.normal); if (!l || l != l) return (Real)-1.; d.normal[0] /= l; d.normal[1] /= l; d.normal[2] /= l; return (Real)1.; }; if (confidence > 0) { pointCount = FEMTreeInitializer::template Initialize< PointData>(tree.spaceRoot(), pointStream, depth, samples, sampleData, true, tree.nodeAllocators[0], tree.initializer(), ProcessDataWithConfidence); } else { pointCount = FEMTreeInitializer::template Initialize< PointData>(tree.spaceRoot(), pointStream, depth, samples, sampleData, true, tree.nodeAllocators[0], tree.initializer(), ProcessData); } } iXForm = xForm.inverse(); //utility::LogDebug("Input Points / Samples: {} / {}", pointCount, // samples.size()); } int kernelDepth = depth - 2; if (kernelDepth < 0) { //utility::LogError( // "[CreateFromPointCloudPoisson] depth (={}) has to be >= 2", // depth); } DenseNodeData solution; { DenseNodeData constraints; InterpolationInfo* iInfo = NULL; int solveDepth = depth; tree.resetNodeIndices(); // Get the kernel density estimator { profiler.start(); density = tree.template setDensityEstimator( samples, kernelDepth, samples_per_node, 1); profiler.dumpOutput("# Got kernel density:"); } // Transform the Hermite samples into a vector field { profiler.start(); normalInfo = new SparseNodeData, NormalSigs>(); std::function, Point&)> ConversionFunction = [](PointData in, Point& out) { // Point n = in.template data<0>(); Point n(in.normal[0], in.normal[1], in.normal[2]); Real l = (Real)Length(n); // It is possible that the samples have non-zero // normals but there are two co-located samples // with negative normals... if (!l) return false; out = n / l; return true; }; std::function, Point&, Real&)> ConversionAndBiasFunction = [&](PointData in, Point& out, Real& bias) { // Point n = in.template data<0>(); Point n(in.normal[0], in.normal[1], in.normal[2]); Real l = (Real)Length(n); // It is possible that the samples have non-zero normals // but there are two co-located samples with negative // normals... if (!l) return false; out = n / l; bias = (Real)(log(l) * confidence_bias / log(1 << (Dim - 1))); return true; }; if (confidence_bias > 0) { *normalInfo = tree.setDataField( NormalSigs(), samples, sampleData, density, pointWeightSum, ConversionAndBiasFunction); } else { *normalInfo = tree.setDataField( NormalSigs(), samples, sampleData, density, pointWeightSum, ConversionFunction); } ThreadPool::Parallel_for(0, normalInfo->size(), [&](unsigned int, size_t i) { (*normalInfo)[i] *= (Real)-1.; }); profiler.dumpOutput("# Got normal field:"); //utility::LogDebug("Point weight / Estimated Area: {:e} / {:e}", // pointWeightSum, pointCount * pointWeightSum); } // Trim the tree and prepare for multigrid { profiler.start(); constexpr int MAX_DEGREE = NORMAL_DEGREE > Degrees::Max() ? NORMAL_DEGREE : Degrees::Max(); tree.template finalizeForMultigrid( full_depth, typename FEMTree::template HasNormalDataFunctor< NormalSigs>(*normalInfo), normalInfo, density); profiler.dumpOutput("# Finalized tree:"); } // Add the FEM constraints { profiler.start(); constraints = tree.initDenseNodeData(Sigs()); typename FEMIntegrator::template Constraint< Sigs, IsotropicUIntPack, NormalSigs, IsotropicUIntPack, Dim> F; unsigned int derivatives2[Dim]; for (unsigned int d = 0; d < Dim; d++) derivatives2[d] = 0; typedef IsotropicUIntPack Derivatives1; typedef IsotropicUIntPack Derivatives2; for (unsigned int d = 0; d < Dim; d++) { unsigned int derivatives1[Dim]; for (unsigned int dd = 0; dd < Dim; dd++) derivatives1[dd] = dd == d ? 1 : 0; F.weights[d] [TensorDerivatives::Index(derivatives1)] [TensorDerivatives::Index( derivatives2)] = 1; } tree.addFEMConstraints(F, *normalInfo, constraints, solveDepth); profiler.dumpOutput("# Set FEM constraints:"); } // Free up the normal info delete normalInfo, normalInfo = NULL; // Add the interpolation constraints if (point_weight > 0) { profiler.start(); if (exact_interpolation) { iInfo = FEMTree:: template InitializeExactPointInterpolationInfo( tree, samples, ConstraintDual( targetValue, (Real)point_weight * pointWeightSum), SystemDual((Real)point_weight * pointWeightSum), true, false); } else { iInfo = FEMTree:: template InitializeApproximatePointInterpolationInfo< Real, 0>( tree, samples, ConstraintDual( targetValue, (Real)point_weight * pointWeightSum), SystemDual((Real)point_weight * pointWeightSum), true, 1); } tree.addInterpolationConstraints(constraints, solveDepth, *iInfo); profiler.dumpOutput("#Set point constraints:"); } //utility::LogDebug( // "Leaf Nodes / Active Nodes / Ghost Nodes: {} / {} / {}", // tree.leaves(), tree.nodes(), tree.ghostNodes()); //utility::LogDebug("Memory Usage: {:.3f} MB", // float(MemoryInfo::Usage()) / (1 << 20)); // Solve the linear system { profiler.start(); typename FEMTree::SolverInfo sInfo; sInfo.cgDepth = 0, sInfo.cascadic = true, sInfo.vCycles = 1, sInfo.iters = iters, sInfo.cgAccuracy = cg_solver_accuracy, sInfo.verbose = false/* utility::Logger::i().verbosity_level_ == utility::VerbosityLevel::Debug */, sInfo.showResidual = false/*utility::Logger::i().verbosity_level_ == utility::VerbosityLevel::Debug*/, sInfo.showGlobalResidual = SHOW_GLOBAL_RESIDUAL_NONE, sInfo.sliceBlockSize = 1; sInfo.baseDepth = base_depth, sInfo.baseVCycles = base_v_cycles; typename FEMIntegrator::template System> F({ 0., 1. }); solution = tree.solveSystem(Sigs(), F, constraints, solveDepth, sInfo, iInfo); profiler.dumpOutput("# Linear system solved:"); if (iInfo) delete iInfo, iInfo = NULL; } } { profiler.start(); double valueSum = 0, weightSum = 0; typename FEMTree::template MultiThreadedEvaluator evaluator(&tree, solution); std::vector valueSums(ThreadPool::NumThreads(), 0), weightSums(ThreadPool::NumThreads(), 0); ThreadPool::Parallel_for( 0, samples.size(), [&](unsigned int thread, size_t j) { ProjectiveData, Real>& sample = samples[j].sample; Real w = sample.weight; if (w > 0) weightSums[thread] += w, valueSums[thread] += evaluator.values(sample.data / sample.weight, thread, samples[j].node)[0] * w; }); for (size_t t = 0; t < valueSums.size(); t++) valueSum += valueSums[t], weightSum += weightSums[t]; isoValue = (Real)(valueSum / weightSum); profiler.dumpOutput("Got average:"); //utility::LogDebug("Iso-Value: {:e} = {:e} / {:e}", isoValue, valueSum, // weightSum); } auto SetVertex = [](Vertex& v, Point p, double w, PointData d) { v = Vertex(p, d, w); }; ExtractMesh, Real>( datax, linear_fit, UIntPack(), std::tuple(), tree, solution, isoValue, &samples, &sampleData, density, SetVertex, iXForm, out_mesh); if (density) { delete density; density = nullptr; } //utility::LogDebug("# Total Solve: {:9.1f} (s), {:9.1f} (MB)", // Time() - startTime, FEMTree::MaxMemoryUsage()); } bool PoissonReconLib::Reconstruct( const Parameters& params, const ICloud& inCloud, IMesh& outMesh ) { if (!inCloud.hasNormals()) { //we need normals return false; } #ifdef WITH_OPENMP ThreadPool::Init((ThreadPool::ParallelType)(int)ThreadPool::OPEN_MP, std::thread::hardware_concurrency()); #else ThreadPool::Init((ThreadPool::ParallelType)(int)ThreadPool::THREAD_POOL, std::thread::hardware_concurrency()); #endif PointStream pointStream(inCloud); switch (params.boundary) { case Parameters::FREE: typedef IsotropicUIntPack::Signature> FEMSigsFree; Execute(pointStream, outMesh, params.depth, params.width, params.scale, params.linear_fit, FEMSigsFree()); break; case Parameters::DIRICHLET: typedef IsotropicUIntPack::Signature> FEMSigsDirichlet; Execute(pointStream, outMesh, params.depth, params.width, params.scale, params.linear_fit, FEMSigsDirichlet()); break; case Parameters::NEUMANN: typedef IsotropicUIntPack::Signature> FEMSigsNeumann; Execute(pointStream, outMesh, params.depth, params.width, params.scale, params.linear_fit, FEMSigsNeumann()); break; default: assert(false); break; } ThreadPool::Terminate(); return true; }