diff --git a/ContextBasedFeature.cpp b/ContextBasedFeature.cpp index 819d32f..459bf89 100644 --- a/ContextBasedFeature.cpp +++ b/ContextBasedFeature.cpp @@ -17,6 +17,9 @@ #include "ContextBasedFeature.h" +//Local +#include "q3DMASCTools.h" + //Qt #include @@ -36,10 +39,21 @@ bool ContextBasedFeature::checkValidity(QString corePointRole, QString &error) c return false; } - unsigned char cloudCount = (cloud1 ? (cloud2 ? 2 : 1) : 0); - if (cloudCount < 2) + if (!cloud1 || !cloud2) { - error = "at least two clouds are required to compute context-based features"; + error = "two clouds are required to compute context-based features"; + return false; + } + + CCLib::ScalarField* classifSF = Tools::GetClassificationSF(cloud2); + if (!classifSF) + { + error = QString("Context cloud (%1) has no classification field").arg(cloud2Label); + return false; + } + if (classifSF->size() < cloud2->size()) + { + error = QString("Context cloud (%1) has an invalid classification field").arg(cloud2Label); return false; } @@ -79,6 +93,15 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, return false; } + CCLib::ScalarField* classifSF = Tools::GetClassificationSF(cloud2); + if (!classifSF || classifSF->size() < cloud2->size()) + { + assert(false); + //already checked by 'checkValidity' + return false; + } + cloud2->setCurrentOutScalarField(cloud2->getScalarFieldIndexByName(classifSF->getName())); + //build the final SF name QString typeStr = ToString(type); QString resultSFName = typeStr + "_" + cloud1Label + "_" + cloud2Label + "_" + QString::number(ctxClassLabel); @@ -104,12 +127,12 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, if (!scaled()) //with 'kNN' neighbors, we can compute the values right away { //get the octree - ccOctree::Shared octree = cloud2->getOctree(); - if (!octree) + ccOctree::Shared octree2 = cloud2->getOctree(); + if (!octree2) { ccLog::Print(QString("Computing octree of cloud %1 (%2 points)").arg(cloud2->getName()).arg(cloud2->size())); - octree = cloud2->computeOctree(progressCb); - if (!octree) + octree2 = cloud2->computeOctree(progressCb); + if (!octree2) { error = "Failed to compute octree (not enough memory?)"; return false; @@ -117,7 +140,7 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, } //now extract the neighborhoods - unsigned char octreeLevel = octree->findBestLevelForAGivenPopulationPerCell(static_cast(std::max(3, kNN))); + unsigned char octreeLevel = octree2->findBestLevelForAGivenPopulationPerCell(static_cast(std::max(3, kNN))); ccLog::Print(QString("[Initial octree level] level = %1").arg(octreeLevel)); unsigned pointCount = corePoints.size(); @@ -130,6 +153,8 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, ccLog::Print(logMessage); CCLib::NormalizedProgress nProgress(progressCb, pointCount); + const ScalarType fClass = static_cast(ctxClassLabel); + QMutex mutex; bool error = false; double meanNeighborhoodSize = 0; @@ -148,46 +173,54 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, ScalarType s = NAN_VALUE; int neighborhoodSize = 0; - if (octree->findPointNeighbourhood(P, &Yk, static_cast(kNN), octreeLevel, maxSquareDist, 0, &neighborhoodSize) >= static_cast(kNN)) + if (octree2->findPointNeighbourhood(P, &Yk, static_cast(kNN), octreeLevel, maxSquareDist, 0, &neighborhoodSize) >= static_cast(kNN)) { CCVector3d sumQ(0, 0, 0); + unsigned validCount = 0; for (int k = 0; k < kNN; ++k) { + //we only consider points with the right class!!! + if (Yk.getCurrentPointScalarValue() != fClass) + continue; sumQ += CCVector3d::fromArray(Yk.getPoint(k)->u); + ++validCount; } - switch (type) + if (validCount) { - case DZ: - s = static_cast(P->z - sumQ.z / kNN); - break; - case DH: - s = static_cast(sqrt(pow(P->x - sumQ.x / kNN, 2.0) + pow(P->y - sumQ.y / kNN, 2.0))); - break; + switch (type) + { + case DZ: + s = static_cast(P->z - sumQ.z / validCount); + break; + case DH: + s = static_cast(sqrt(pow(P->x - sumQ.x / validCount, 2.0) + pow(P->y - sumQ.y / validCount, 2.0))); + break; + } } + } - if (i && (i % tenth) == 0) + if (i && (i % tenth) == 0) + { + double density = meanNeighborhoodSize / tenth; + if (density < 1.1) { - double density = meanNeighborhoodSize / tenth; - if (density < 1.1) - { - if (octreeLevel + 1 < CCLib::DgmOctree::MAX_OCTREE_LEVEL) - ++octreeLevel; - } - else while (density > 2.9) - { - if (octreeLevel <= 5) - break; - --octreeLevel; - density /= 2.0; - } - ccLog::Print(QString("[Adaptative octree level] Mean neighborhood size: %1 --> new level = %2").arg(meanNeighborhoodSize / tenth).arg(octreeLevel)); - meanNeighborhoodSize = 0; + if (octreeLevel + 1 < CCLib::DgmOctree::MAX_OCTREE_LEVEL) + ++octreeLevel; } - else + else while (density > 2.9) { - meanNeighborhoodSize += neighborhoodSize; + if (octreeLevel <= 5) + break; + --octreeLevel; + density /= 2.0; } + ccLog::Print(QString("[Adaptative octree level] Mean neighborhood size: %1 --> new level = %2").arg(meanNeighborhoodSize / tenth).arg(octreeLevel)); + meanNeighborhoodSize = 0; + } + else + { + meanNeighborhoodSize += neighborhoodSize; } sf->setValue(i, s); @@ -225,19 +258,32 @@ bool ContextBasedFeature::prepare( const CorePoints& corePoints, bool ContextBasedFeature::computeValue(CCLib::DgmOctree::NeighboursSet& pointsInNeighbourhood, const CCVector3& queryPoint, ScalarType& outputValue) const { + const ScalarType fClass = static_cast(ctxClassLabel); + CCVector3d sumQ(0, 0, 0); + unsigned validCount = 0; for (CCLib::DgmOctree::PointDescriptor& Pd : pointsInNeighbourhood) { + //we only consider points with the right class!!! + if (cloud2->getPointScalarValue(Pd.pointIndex) != fClass) + continue; sumQ += CCVector3d::fromArray(Pd.point->u); + ++validCount; + } + + if (validCount == 0) + { + outputValue = NAN_VALUE; + return true; } switch (type) { case DZ: - outputValue = static_cast(queryPoint.z - sumQ.z / kNN); + outputValue = static_cast(queryPoint.z - sumQ.z / validCount); break; case DH: - outputValue = static_cast(sqrt(pow(queryPoint.x - sumQ.x / kNN, 2.0) + pow(queryPoint.y - sumQ.y / kNN, 2.0))); + outputValue = static_cast(sqrt(pow(queryPoint.x - sumQ.x / validCount, 2.0) + pow(queryPoint.y - sumQ.y / validCount, 2.0))); break; default: assert(false); diff --git a/q3DMASC.cpp b/q3DMASC.cpp index 18ca002..644f39d 100644 --- a/q3DMASC.cpp +++ b/q3DMASC.cpp @@ -310,7 +310,7 @@ void q3DMASCPlugin::doTrainAction() mainCloudLabel = corePoints.role; } - if (!masc::Classifier::GetClassificationSF(corePoints.origin)) + if (!masc::Tools::GetClassificationSF(corePoints.origin)) { m_app->dispToConsole("Missing 'Classification' field on core points cloud", ccMainAppInterface::ERR_CONSOLE_MESSAGE); return; diff --git a/q3DMASCClassifier.cpp b/q3DMASCClassifier.cpp index a10dbc7..1f8f2d2 100644 --- a/q3DMASCClassifier.cpp +++ b/q3DMASCClassifier.cpp @@ -19,6 +19,7 @@ //Local #include "ScalarFieldWrappers.h" +#include "q3DMASCTools.h" //qCC_db #include @@ -120,7 +121,7 @@ bool Classifier::classify( const Feature::Source::Set& featureSources, } //look for the classification field - CCLib::ScalarField* classificationSF = GetClassificationSF(cloud); + CCLib::ScalarField* classificationSF = Tools::GetClassificationSF(cloud); if (classificationSF) { @@ -294,7 +295,7 @@ bool Classifier::evaluate(const Feature::Source::Set& featureSources, } //look for the classification field - CCLib::ScalarField* classifSF = GetClassificationSF(testCloud); + CCLib::ScalarField* classifSF = Tools::GetClassificationSF(testCloud); if (!classifSF || classifSF->size() < testCloud->size()) { assert(false); @@ -442,7 +443,7 @@ bool Classifier::train( const ccPointCloud* cloud, } //look for the classification field - CCLib::ScalarField* classifSF = GetClassificationSF(cloud); + CCLib::ScalarField* classifSF = Tools::GetClassificationSF(cloud); if (!classifSF || classifSF->size() < cloud->size()) { assert(false); @@ -674,21 +675,3 @@ bool Classifier::fromFile(QString filename, QWidget* parentWidget/*=nullptr*/) return true; } - -CCLib::ScalarField* Classifier::GetClassificationSF(const ccPointCloud* cloud) -{ - if (!cloud) - { - //invalid input cloud - assert(false); - return nullptr; - } - //look for the classification field - int classifSFIdx = cloud->getScalarFieldIndexByName(LAS_FIELD_NAMES[LAS_CLASSIFICATION]); //LAS_FIELD_NAMES[LAS_CLASSIFICATION] = "Classification" - if (classifSFIdx < 0) - { - return nullptr; - } - return cloud->getScalarField(classifSFIdx); -} - diff --git a/q3DMASCClassifier.h b/q3DMASCClassifier.h index 8baaf79..b8dd0b2 100644 --- a/q3DMASCClassifier.h +++ b/q3DMASCClassifier.h @@ -85,9 +85,6 @@ namespace masc inline cv::Mat getVarImportance() const { return m_rtrees->getVarImportance(); } - //! Helper: returns the classification SF associated to a cloud (if any) - static CCLib::ScalarField* GetClassificationSF(const ccPointCloud* cloud); - protected: //! Random trees (OpenCV) diff --git a/q3DMASCTools.cpp b/q3DMASCTools.cpp index aabfbc3..e57f1dd 100644 --- a/q3DMASCTools.cpp +++ b/q3DMASCTools.cpp @@ -29,6 +29,9 @@ #include #include +//qPDALIO +#include "../../core/IO/qPDALIO/src/LASFields.h" + //Qt #include #include @@ -1373,3 +1376,20 @@ bool Tools::RandomSubset(ccPointCloud* cloud, float ratio, CCLib::ReferenceCloud return true; } + +CCLib::ScalarField* Tools::GetClassificationSF(const ccPointCloud* cloud) +{ + if (!cloud) + { + //invalid input cloud + assert(false); + return nullptr; + } + //look for the classification field + int classifSFIdx = cloud->getScalarFieldIndexByName(LAS_FIELD_NAMES[LAS_CLASSIFICATION]); //LAS_FIELD_NAMES[LAS_CLASSIFICATION] = "Classification" + if (classifSFIdx < 0) + { + return nullptr; + } + return cloud->getScalarField(classifSFIdx); +} diff --git a/q3DMASCTools.h b/q3DMASCTools.h index 8ac0d88..318a790 100644 --- a/q3DMASCTools.h +++ b/q3DMASCTools.h @@ -60,6 +60,9 @@ namespace masc static bool RandomSubset(ccPointCloud* cloud, float ratio, CCLib::ReferenceCloud* inRatioSubset, CCLib::ReferenceCloud* outRatioSubset); static CCLib::ScalarField* RetrieveSF(const ccPointCloud* cloud, const QString& sfName, bool caseSensitive = true); + + //! Helper: returns the classification SF associated to a cloud (if any) + static CCLib::ScalarField* GetClassificationSF(const ccPointCloud* cloud); }; }; //namespace masc