Files
q3DMASC/NeighborhoodFeature.cpp
T

364 lines
8.7 KiB
C++
Raw Normal View History

//##########################################################################
//# #
//# CLOUDCOMPARE PLUGIN: q3DMASC #
//# #
//# 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: Dimitri Lague / CNRS / UEB #
//# #
//##########################################################################
#include "NeighborhoodFeature.h"
2018-12-01 22:40:04 +01:00
//CCLib
#include <DgmOctreeReferenceCloud.h>
2020-06-04 00:32:27 +02:00
#include <Neighbourhood.h>
2018-12-01 22:40:04 +01:00
#include <Jacobi.h>
using namespace masc;
bool NeighborhoodFeature::checkValidity(QString corePointRole, QString &error) const
2018-12-01 22:40:04 +01:00
{
if (!Feature::checkValidity(corePointRole, error))
2018-12-01 22:40:04 +01:00
{
return false;
}
2018-12-15 20:40:58 +01:00
if (type == Invalid)
{
assert(false);
error = "invalid feature type";
return false;
}
2018-12-01 22:40:04 +01:00
if (stat != Feature::NO_STAT)
{
error = "Neighborhood features shouldn't be associated to a STAT measure";
return false;
}
if (cloud2 && op == NO_OPERATION)
{
error = "Feature has a second cloud associated but no MATH operation is defined";
return false;
}
2019-03-28 21:29:03 +01:00
if (std::isnan(scale))
{
error = "No scale defined";
return false;
}
2018-12-01 22:40:04 +01:00
return true;
}
bool NeighborhoodFeature::prepare( const CorePoints& corePoints,
QString& error,
2020-06-04 00:32:27 +02:00
CCCoreLib::GenericProgressCallback* progressCb/*=nullptr*/,
SFCollector* generatedScalarFields/*=nullptr*/)
{
2018-12-01 22:40:04 +01:00
if (!cloud1 || !corePoints.cloud)
{
//invalid input
assert(false);
error = "internal error (no input core points)";
return false;
}
if (!checkValidity(corePoints.role, error))
2018-12-01 22:40:04 +01:00
{
assert(false);
return false;
}
//build the final SF name
QString resultSFName = ToString(type) + "_" + cloud1Label;
if (cloud2)
{
//include the math operation as well if necessary!
resultSFName += "_" + Feature::OpToString(op) + "_" + cloud2Label;
}
resultSFName += "@" + QString::number(scale);
//and the scalar field
assert(!sf1);
2019-05-03 23:52:33 +02:00
sf1 = PrepareSF(corePoints.cloud, qPrintable(resultSFName), generatedScalarFields, SFCollector::CAN_REMOVE);
2018-12-01 22:40:04 +01:00
if (!sf1)
{
2018-12-15 20:40:58 +01:00
error = QString("Failed to prepare scalar %1 @ scale %2").arg(resultSFName).arg(scale);
2018-12-01 22:40:04 +01:00
return false;
}
2019-03-26 14:28:27 +01:00
source.name = sf1->getName();
2018-12-01 22:40:04 +01:00
if (cloud2 && op != Feature::NO_OPERATION)
{
QString resultSFName2 = ToString(type) + "_" + cloud2Label + "@" + QString::number(scale);
keepSF2 = (corePoints.cloud->getScalarFieldIndexByName(qPrintable(resultSFName2)) >= 0); //we remember that the scalar field was already existing!
assert(!sf2);
2019-05-03 23:52:33 +02:00
sf2 = PrepareSF(corePoints.cloud, qPrintable(resultSFName2), generatedScalarFields, SFCollector::ALWAYS_REMOVE);
2018-12-01 22:40:04 +01:00
if (!sf2)
{
error = QString("Failed to prepare scalar field for %1 @ scale %2").arg(cloud2Label).arg(scale);
return false;
}
}
return true;
}
bool NeighborhoodFeature::finish(const CorePoints& corePoints, QString& error)
{
if (!corePoints.cloud)
{
//invalid input
assert(false);
error = "internal error (no input core points)";
return false;
}
bool success = true;
if (sf1)
{
sf1->computeMinAndMax();
//update display
//if (corePoints.cloud->getDisplay())
{
int sfIndex1 = corePoints.cloud->getScalarFieldIndexByName(sf1->getName());
corePoints.cloud->setCurrentDisplayedScalarField(sfIndex1);
//corePoints.cloud->getDisplay()->redraw();
//QCoreApplication::processEvents();
}
}
if (sf2)
{
//now perform the math operation
if (op != Feature::NO_OPERATION)
{
if (!PerformMathOp(sf1, sf2, op))
{
error = "Failed to perform the MATH operation";
success = false;
}
}
if (keepSF2)
{
sf2->computeMinAndMax();
}
else
{
int sfIndex2 = corePoints.cloud->getScalarFieldIndexByName(sf2->getName());
if (sfIndex2 >= 0)
{
corePoints.cloud->deleteScalarField(sfIndex2);
}
else
{
assert(false);
sf2->release();
}
sf2 = nullptr;
}
}
return success;
}
QString NeighborhoodFeature::toString() const
{
//use the default keyword + the scale
QString description = ToString(type) + "_SC" + QString::number(scale);
description += "_" + cloud1Label;
if (cloud2 && !cloud2Label.isEmpty())
{
description += "_" + cloud2Label;
if (op != NO_OPERATION)
{
description += "_" + OpToString(op);
}
}
return description;
}
2020-06-04 00:32:27 +02:00
bool NeighborhoodFeature::computeValue(CCCoreLib::DgmOctree::NeighboursSet& pointsInNeighbourhood, const CCVector3& queryPoint, double& outputValue) const
2018-12-01 22:40:04 +01:00
{
outputValue = std::numeric_limits<double>::quiet_NaN();
size_t kNN = pointsInNeighbourhood.size();
if (kNN == 0)
{
assert(false);
return false;
}
switch (type)
{
//features relying on the PCA
case PCA1:
case PCA2:
2019-03-22 14:50:45 +01:00
case PCA3:
2018-12-01 22:40:04 +01:00
case SPHER:
case LINEA:
case PLANA:
{
2020-06-04 00:32:27 +02:00
CCCoreLib::Neighbourhood::GeomFeature f;
2018-12-02 19:32:47 +01:00
switch (type)
2018-12-01 22:40:04 +01:00
{
2018-12-02 19:32:47 +01:00
case PCA1:
2020-06-04 00:32:27 +02:00
f = CCCoreLib::Neighbourhood::PCA1;
2018-12-02 19:32:47 +01:00
break;
case PCA2:
2020-06-04 00:32:27 +02:00
f = CCCoreLib::Neighbourhood::PCA2;
2018-12-02 19:32:47 +01:00
break;
2019-03-22 14:50:45 +01:00
case PCA3:
2020-06-04 00:32:27 +02:00
f = CCCoreLib::Neighbourhood::SurfaceVariation;
2019-03-22 14:50:45 +01:00
break;
2018-12-02 19:32:47 +01:00
case SPHER:
2020-06-04 00:32:27 +02:00
f = CCCoreLib::Neighbourhood::Sphericity;
2018-12-02 19:32:47 +01:00
break;
case LINEA:
2020-06-04 00:32:27 +02:00
f = CCCoreLib::Neighbourhood::Linearity;
2018-12-02 19:32:47 +01:00
break;
case PLANA:
2020-06-04 00:32:27 +02:00
f = CCCoreLib::Neighbourhood::Planarity;
2018-12-02 19:32:47 +01:00
break;
default:
//impossible
assert(false);
2018-12-01 22:40:04 +01:00
return false;
}
2018-12-02 19:32:47 +01:00
2020-06-04 00:32:27 +02:00
CCCoreLib::DgmOctreeReferenceCloud neighboursCloud(&pointsInNeighbourhood, static_cast<unsigned>(kNN));
CCCoreLib::Neighbourhood Z(&neighboursCloud);
2018-12-02 19:32:47 +01:00
outputValue = Z.computeFeature(f);
}
break;
case FOM:
{
2020-06-04 00:32:27 +02:00
CCCoreLib::DgmOctreeReferenceCloud neighboursCloud(&pointsInNeighbourhood, static_cast<unsigned>(kNN));
CCCoreLib::Neighbourhood Z(&neighboursCloud);
2018-12-02 19:32:47 +01:00
outputValue = Z.computeMomentOrder1(queryPoint);
2018-12-01 22:40:04 +01:00
}
break;
2019-03-29 21:36:35 +01:00
case Dip:
2018-12-01 22:40:04 +01:00
case DipDir:
if (kNN >= 3)
{
2020-06-04 00:32:27 +02:00
CCCoreLib::DgmOctreeReferenceCloud neighboursCloud(&pointsInNeighbourhood, static_cast<unsigned>(kNN));
CCCoreLib::Neighbourhood Z(&neighboursCloud);
2018-12-01 22:40:04 +01:00
const CCVector3* N = Z.getLSPlaneNormal();
if (N)
{
//force +Z
2020-06-04 00:32:27 +02:00
CCVector3 Np = (N->z < 0 ? -CCCoreLib::PC_ONE * *N : *N);
2018-12-01 22:40:04 +01:00
PointCoordinateType dip_deg, dipDir_deg;
ccNormalVectors::ConvertNormalToDipAndDipDir(Np, dip_deg, dipDir_deg);
2019-03-29 21:36:35 +01:00
outputValue = (type == Dip ? dip_deg : dipDir_deg);
2018-12-01 22:40:04 +01:00
}
}
break;
case NBPTS:
outputValue = static_cast<double>(kNN);
break;
2018-12-02 19:32:47 +01:00
case ROUGH:
{
2020-06-04 00:32:27 +02:00
CCCoreLib::DgmOctreeReferenceCloud neighboursCloud(&pointsInNeighbourhood, static_cast<unsigned>(kNN));
CCCoreLib::Neighbourhood Z(&neighboursCloud);
2018-12-02 19:32:47 +01:00
outputValue = Z.computeRoughness(queryPoint);
}
break;
2018-12-01 22:40:04 +01:00
case CURV:
2018-12-02 19:32:47 +01:00
{
2020-06-04 00:32:27 +02:00
CCCoreLib::DgmOctreeReferenceCloud neighboursCloud(&pointsInNeighbourhood, static_cast<unsigned>(kNN));
CCCoreLib::Neighbourhood Z(&neighboursCloud);
outputValue = Z.computeCurvature(queryPoint, CCCoreLib::Neighbourhood::MEAN_CURV); //TODO: is it really the default one?
2018-12-02 19:32:47 +01:00
}
break;
2018-12-01 22:40:04 +01:00
case ZRANGE:
case Zmax:
case Zmin:
if (kNN >= 2)
{
PointCoordinateType minZ, maxZ;
minZ = maxZ = pointsInNeighbourhood[0].point->z;
for (size_t i = 1; i < kNN; ++i)
{
if (minZ < pointsInNeighbourhood[i].point->z)
minZ = pointsInNeighbourhood[i].point->z;
else if (maxZ > pointsInNeighbourhood[i].point->z)
maxZ = pointsInNeighbourhood[i].point->z;
}
if (type == ZRANGE)
{
outputValue = maxZ - minZ;
}
else if (type == Zmax)
{
outputValue = maxZ - queryPoint.z;
}
2019-03-28 21:29:03 +01:00
else if (type == Zmin)
2018-12-01 22:40:04 +01:00
{
outputValue = queryPoint.z - minZ;
}
else
{
//impossible
assert(false);
}
}
case ANISO:
if (kNN >= 3)
{
2020-06-04 00:32:27 +02:00
CCCoreLib::DgmOctreeReferenceCloud neighboursCloud(&pointsInNeighbourhood, static_cast<unsigned>(kNN));
CCCoreLib::Neighbourhood Z(&neighboursCloud);
2018-12-01 22:40:04 +01:00
const CCVector3* G = Z.getGravityCenter();
if (G)
{
double r = sqrt(pointsInNeighbourhood.back().squareDistd);
if (r > std::numeric_limits<double>::epsilon())
{
double d = (queryPoint - *G).normd();
//Ratio of distance to center of mass and radius of sphere
outputValue = d / r;
}
}
}
break;
2018-12-02 19:32:47 +01:00
//case LINEF:
//case ORIENF:
2018-12-01 22:40:04 +01:00
default:
{
2018-12-02 19:32:47 +01:00
ccLog::Warning("Unhandled feature");
2018-12-01 22:40:04 +01:00
assert(false);
return false;
}
}
return true;
}