2018-11-04 11:21:50 +01:00
//##########################################################################
//# #
//# 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 #
//# #
//##########################################################################
2018-11-04 19:23:11 +01:00
# include "PointFeature.h"
2018-11-04 11:21:50 +01:00
//Local
# include "q3DMASCTools.h"
2019-03-23 20:42:01 +01:00
//qPDALIO
2022-12-08 18:10:38 +01:00
# ifdef PLUGIN_IO_QPDAL
2020-06-04 00:32:27 +02:00
# include "../../core/IO/qPDALIO/include/LASFields.h"
2022-12-08 18:10:38 +01:00
# else
# include "../../core/IO/qLASIO/include/LasDetails.h"
enum LAS_FIELDS {
LAS_X = 0 ,
LAS_Y = 1 ,
LAS_Z = 2 ,
LAS_INTENSITY = 3 ,
LAS_RETURN_NUMBER = 4 ,
LAS_NUMBER_OF_RETURNS = 5 ,
LAS_SCAN_DIRECTION = 6 ,
LAS_FLIGHT_LINE_EDGE = 7 ,
LAS_CLASSIFICATION = 8 ,
LAS_SCAN_ANGLE_RANK = 9 ,
LAS_USER_DATA = 10 ,
LAS_POINT_SOURCE_ID = 11 ,
LAS_RED = 12 ,
LAS_GREEN = 13 ,
LAS_BLUE = 14 ,
LAS_TIME = 15 ,
LAS_EXTRA = 16 ,
//Sub fields
LAS_CLASSIF_VALUE = 17 ,
LAS_CLASSIF_SYNTHETIC = 18 ,
LAS_CLASSIF_KEYPOINT = 19 ,
LAS_CLASSIF_WITHHELD = 20 ,
LAS_CLASSIF_OVERLAP = 21 ,
//Invald flag
LAS_INVALID = 255
} ;
constexpr const char * LAS_FIELD_NAMES [ 22 ] = { " X " ,
" Y " ,
" Z " ,
LasNames : : Intensity ,
LasNames : : ReturnNumber ,
LasNames : : NumberOfReturns ,
LasNames : : ScanDirectionFlag ,
LasNames : : EdgeOfFlightLine ,
LasNames : : Classification ,
LasNames : : ScanAngleRank ,
LasNames : : UserData ,
LasNames : : PointSourceId ,
" Red " ,
" Green " ,
" Blue " ,
LasNames : : GpsTime ,
" extra " ,
" [Classif] Value " ,
" [Classif] Synthetic flag " ,
" [Classif] Key-point flag " ,
" [Classif] Withheld flag " ,
" [Classif] Overlap flag " ,
} ;
# endif
2018-11-04 11:21:50 +01:00
//qCC_db
# include <ccPointCloud.h>
# include <ccScalarField.h>
2018-11-04 18:15:49 +01:00
//CCLib
# include <WeibullDistribution.h>
2018-11-04 11:21:50 +01:00
//system
# include <assert.h>
2018-12-01 14:39:11 +01:00
//Qt
# include <QCoreApplication>
2019-03-23 20:42:01 +01:00
# include <QMutex>
2018-12-01 14:39:11 +01:00
2018-11-04 11:21:50 +01:00
static const char * s_echoRatioSFName = " EchoRat " ;
static const char * s_NIRSFName = " NIR " ;
static const char * s_M3C2SFName = " M3C2 distance " ;
static const char * s_PCVSFName = " Illuminance (PCV) " ;
static const char * s_normDipSFName = " Norm dip " ;
static const char * s_normDipDirSFName = " Norm dip dir. " ;
using namespace masc ;
2019-03-25 22:21:40 +01:00
bool PointFeature : : checkValidity ( QString corePointRole , QString & error ) const
2018-11-04 18:15:49 +01:00
{
2019-03-25 22:21:40 +01:00
if ( ! Feature : : checkValidity ( corePointRole , error ) )
2018-11-04 18:15:49 +01:00
{
return false ;
}
2018-12-15 20:40:58 +01:00
if ( type = = Invalid )
{
assert ( false ) ;
error = " invalid feature type " ;
return false ;
}
2018-11-04 18:15:49 +01:00
assert ( cloud1 ) ;
2019-03-25 22:21:40 +01:00
if ( ! corePointRole . isEmpty ( ) & & ! scaled ( ) & & cloud1Label ! = corePointRole ) //in some cases, we don't know the role of the core points yet!
{
error = " Scale-less features can only be computed on the core points / classified cloud " ;
return false ;
}
2018-12-01 22:40:04 +01:00
if ( scaled ( ) & & stat = = NO_STAT )
{
error = " scaled point features need a STAT measure to be defined " ;
return false ;
}
2019-03-23 20:42:01 +01:00
if ( op ! = NO_OPERATION & & ! cloud2 )
2018-12-01 22:40:04 +01:00
{
2019-03-23 20:42:01 +01:00
error = " math operations require two clouds " ;
2018-12-01 22:40:04 +01:00
return false ;
}
2018-11-04 18:15:49 +01:00
switch ( type )
{
case PointFeature : : Intensity :
{
if ( cloud1 - > getScalarFieldIndexByName ( LAS_FIELD_NAMES [ LAS_INTENSITY ] ) < 0 )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no '%1' scalar field " ) . arg ( cloud1 - > getName ( ) ) . arg ( LAS_FIELD_NAMES [ LAS_INTENSITY ] ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
}
case PointFeature : : X :
case PointFeature : : Y :
case PointFeature : : Z :
return true ;
case PointFeature : : NbRet :
{
if ( cloud1 - > getScalarFieldIndexByName ( LAS_FIELD_NAMES [ LAS_NUMBER_OF_RETURNS ] ) < 0 )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no '%1' scalar field " ) . arg ( cloud1 - > getName ( ) ) . arg ( LAS_FIELD_NAMES [ LAS_NUMBER_OF_RETURNS ] ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
}
case PointFeature : : RetNb :
{
if ( cloud1 - > getScalarFieldIndexByName ( LAS_FIELD_NAMES [ LAS_RETURN_NUMBER ] ) < 0 )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no '%1' scalar field " ) . arg ( cloud1 - > getName ( ) ) . arg ( LAS_FIELD_NAMES [ LAS_RETURN_NUMBER ] ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
}
case PointFeature : : EchoRat :
{
if ( cloud1 - > getScalarFieldIndexByName ( LAS_FIELD_NAMES [ LAS_NUMBER_OF_RETURNS ] ) < 0 )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no '%1' scalar field " ) . arg ( cloud1 - > getName ( ) ) . arg ( LAS_FIELD_NAMES [ LAS_NUMBER_OF_RETURNS ] ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
if ( cloud1 - > getScalarFieldIndexByName ( LAS_FIELD_NAMES [ LAS_RETURN_NUMBER ] ) < 0 )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no '%1' scalar field " ) . arg ( cloud1 - > getName ( ) ) . arg ( LAS_FIELD_NAMES [ LAS_RETURN_NUMBER ] ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
}
case PointFeature : : R :
case PointFeature : : G :
case PointFeature : : B :
if ( ! cloud1 - > hasColors ( ) )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no RGB color " ) . arg ( cloud1 - > getName ( ) ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
case PointFeature : : NIR :
{
if ( cloud1 - > getScalarFieldIndexByName ( s_NIRSFName ) < 0 )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no '%1' scalar field " ) . arg ( cloud1 - > getName ( ) ) . arg ( s_NIRSFName ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
}
2019-03-29 21:36:35 +01:00
case PointFeature : : Dip :
2018-11-04 18:15:49 +01:00
case PointFeature : : DipDir :
{
if ( ! cloud1 - > hasNormals ( ) )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no normals " ) . arg ( cloud1 - > getName ( ) ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
}
case PointFeature : : M3C2 :
{
if ( cloud1 - > getScalarFieldIndexByName ( s_M3C2SFName ) < 0 )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no '%1' scalar field " ) . arg ( cloud1 - > getName ( ) ) . arg ( s_M3C2SFName ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
}
case PointFeature : : PCV :
{
if ( cloud1 - > getScalarFieldIndexByName ( s_PCVSFName ) < 0 )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no '%1' scalar field " ) . arg ( cloud1 - > getName ( ) ) . arg ( s_PCVSFName ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
}
case PointFeature : : SF :
if ( sourceSFIndex > = static_cast < int > ( cloud1 - > getNumberOfScalarFields ( ) ) )
{
2019-03-28 21:29:03 +01:00
error = QString ( " Cloud %0 has no scalar field #%1 " ) . arg ( cloud1 - > getName ( ) ) . arg ( sourceSFIndex ) ;
2018-11-04 18:15:49 +01:00
return false ;
}
return true ;
default :
break ;
}
return true ;
}
2019-03-23 20:42:01 +01:00
IScalarFieldWrapper : : Shared PointFeature : : retrieveField ( ccPointCloud * cloud , QString & error )
2018-11-04 11:21:50 +01:00
{
if ( ! cloud )
{
assert ( false ) ;
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( nullptr ) ;
2018-11-04 11:21:50 +01:00
}
switch ( type )
{
case PointFeature : : Intensity :
{
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * sf = Tools : : RetrieveSF ( cloud , LAS_FIELD_NAMES [ LAS_INTENSITY ] , false ) ;
2018-11-04 11:21:50 +01:00
if ( ! sf )
{
error = " Cloud has no 'intensity' scalar field " ;
return nullptr ;
}
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ScalarFieldWrapper ( sf ) ) ;
2018-11-04 11:21:50 +01:00
}
case PointFeature : : X :
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new DimScalarFieldWrapper ( cloud , DimScalarFieldWrapper : : DimX ) ) ;
2018-11-04 11:21:50 +01:00
case PointFeature : : Y :
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new DimScalarFieldWrapper ( cloud , DimScalarFieldWrapper : : DimY ) ) ;
2018-11-04 11:21:50 +01:00
case PointFeature : : Z :
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new DimScalarFieldWrapper ( cloud , DimScalarFieldWrapper : : DimZ ) ) ;
2018-11-04 11:21:50 +01:00
case PointFeature : : NbRet :
{
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * sf = Tools : : RetrieveSF ( cloud , LAS_FIELD_NAMES [ LAS_NUMBER_OF_RETURNS ] , false ) ;
2018-11-04 11:21:50 +01:00
if ( ! sf )
{
error = " Cloud has no 'number of returns' scalar field " ;
return nullptr ;
}
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ScalarFieldWrapper ( sf ) ) ;
2018-11-04 11:21:50 +01:00
}
case PointFeature : : RetNb :
{
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * sf = Tools : : RetrieveSF ( cloud , LAS_FIELD_NAMES [ LAS_RETURN_NUMBER ] , false ) ;
2018-11-04 11:21:50 +01:00
if ( ! sf )
{
error = " Cloud has no 'return number' scalar field " ;
return nullptr ;
}
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ScalarFieldWrapper ( sf ) ) ;
2018-11-04 11:21:50 +01:00
}
case PointFeature : : EchoRat :
{
//retrieve the two scalar fields 'p/q'
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * numberOfRetSF = Tools : : RetrieveSF ( cloud , LAS_FIELD_NAMES [ LAS_NUMBER_OF_RETURNS ] , false ) ;
2018-11-04 11:21:50 +01:00
if ( ! numberOfRetSF )
{
error = " Can't compute the 'echo ratio' field: no 'Number of Return' SF available " ;
return nullptr ;
}
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * retNumberSF = Tools : : RetrieveSF ( cloud , LAS_FIELD_NAMES [ LAS_RETURN_NUMBER ] , false ) ;
2018-11-04 11:21:50 +01:00
if ( ! retNumberSF )
{
error = " Can't compute the 'echo ratio' field: no 'Return number' SF available " ;
return nullptr ;
}
if ( retNumberSF - > size ( ) ! = numberOfRetSF - > size ( ) | | retNumberSF - > size ( ) ! = cloud - > size ( ) )
{
error = " Internal error (inconsistent scalar fields) " ;
return nullptr ;
}
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ScalarFieldRatioWrapper ( retNumberSF , numberOfRetSF , " EchoRat " ) ) ;
2018-11-04 11:21:50 +01:00
}
case PointFeature : : R :
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ColorScalarFieldWrapper ( cloud , ColorScalarFieldWrapper : : Red ) ) ;
2018-11-04 11:21:50 +01:00
case PointFeature : : G :
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ColorScalarFieldWrapper ( cloud , ColorScalarFieldWrapper : : Green ) ) ;
2018-11-04 11:21:50 +01:00
case PointFeature : : B :
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ColorScalarFieldWrapper ( cloud , ColorScalarFieldWrapper : : Blue ) ) ;
2018-11-04 11:21:50 +01:00
case PointFeature : : NIR :
{
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * sf = Tools : : RetrieveSF ( cloud , s_NIRSFName , false ) ;
2018-11-04 11:21:50 +01:00
if ( ! sf )
{
error = " Cloud has no 'NIR' scalar field " ;
return nullptr ;
}
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ScalarFieldWrapper ( sf ) ) ;
2018-11-04 11:21:50 +01:00
}
2019-03-29 21:36:35 +01:00
case PointFeature : : Dip :
2018-11-04 11:21:50 +01:00
case PointFeature : : DipDir :
{
//we need normals to compute the dip and dip direction!
if ( ! cloud - > hasNormals ( ) )
{
error = " Cloud has no normals: can't compute dip or dip dir. angles " ;
return nullptr ;
}
2019-03-29 21:36:35 +01:00
return IScalarFieldWrapper : : Shared ( new NormDipAndDipDirFieldWrapper ( cloud , type = = PointFeature : : Dip ? NormDipAndDipDirFieldWrapper : : Dip : NormDipAndDipDirFieldWrapper : : DipDir ) ) ;
2018-11-04 11:21:50 +01:00
}
case PointFeature : : M3C2 :
{
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * sf = Tools : : RetrieveSF ( cloud , s_M3C2SFName , true ) ;
2018-11-04 11:21:50 +01:00
if ( ! sf )
{
error = " Cloud has no 'm3c2 distance' scalar field " ;
return nullptr ;
}
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ScalarFieldWrapper ( sf ) ) ;
2018-11-04 11:21:50 +01:00
}
case PointFeature : : PCV :
{
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * sf = Tools : : RetrieveSF ( cloud , s_PCVSFName , true ) ;
2018-11-04 11:21:50 +01:00
if ( ! sf )
{
error = " Cloud has no 'PCV/Illuminance' scalar field " ;
return nullptr ;
}
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ScalarFieldWrapper ( sf ) ) ;
2018-11-04 11:21:50 +01:00
}
case PointFeature : : SF :
if ( sourceSFIndex < 0 | | sourceSFIndex > = static_cast < int > ( cloud - > getNumberOfScalarFields ( ) ) )
{
error = QString ( " Can't retrieve the specified SF: invalid index (%1) " ) . arg ( sourceSFIndex ) ;
return nullptr ;
}
2019-03-23 20:42:01 +01:00
return IScalarFieldWrapper : : Shared ( new ScalarFieldWrapper ( cloud - > getScalarField ( sourceSFIndex ) ) ) ;
2018-11-04 11:21:50 +01:00
default :
break ;
}
error = " Unhandled feature type " ;
return nullptr ;
}
2019-03-23 20:42:01 +01:00
static bool ComputeMathOpWithNearestNeighbor ( const CorePoints & corePoints ,
const IScalarFieldWrapper & field1 ,
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * outSF ,
2019-03-23 20:42:01 +01:00
ccPointCloud & cloud2 ,
const IScalarFieldWrapper & field2 ,
masc : : Feature : : Operation op ,
QString & error ,
2020-06-04 00:32:27 +02:00
CCCoreLib : : GenericProgressCallback * progressCb = nullptr )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
if ( op = = masc : : Feature : : NO_OPERATION | | ! outSF | | outSF - > size ( ) ! = corePoints . size ( ) )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
//invalid input parameters
2018-11-04 11:21:50 +01:00
assert ( false ) ;
2019-03-23 20:42:01 +01:00
error = " invalid input parameters " ;
2018-11-04 11:21:50 +01:00
return false ;
}
2019-03-23 20:42:01 +01:00
ccOctree : : Shared octree = cloud2 . getOctree ( ) ;
if ( ! octree )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
octree = cloud2 . computeOctree ( progressCb ) ;
if ( ! octree )
{
error = " failed to compute octree on cloud " + cloud2 . getName ( ) ;
return false ;
}
2018-11-04 11:21:50 +01:00
}
2019-03-23 20:42:01 +01:00
//now extract the neighborhoods
unsigned char octreeLevel = octree - > findBestLevelForAGivenPopulationPerCell ( 3 ) ;
ccLog : : Print ( QString ( " [Initial octree level] level = %1 " ) . arg ( octreeLevel ) ) ;
unsigned pointCount = corePoints . size ( ) ;
QString logMessage = QString ( " Extracting %1 core points nearest neighbors in cloud %2 " ) . arg ( pointCount ) . arg ( cloud2 . getName ( ) ) ;
if ( progressCb )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
progressCb - > setMethodTitle ( " Compute math operation " ) ;
progressCb - > setInfo ( qPrintable ( logMessage ) ) ;
2018-11-04 11:21:50 +01:00
}
2019-03-23 20:42:01 +01:00
ccLog : : Print ( logMessage ) ;
2020-06-04 00:32:27 +02:00
CCCoreLib : : NormalizedProgress nProgress ( progressCb , pointCount ) ;
2018-11-04 11:21:50 +01:00
2019-03-23 20:42:01 +01:00
QMutex mutex ;
double meanNeighborhoodSize = 0 ;
int tenth = pointCount / 10 ;
error . clear ( ) ;
# ifndef _DEBUG
# if defined(_OPENMP)
# pragma omp parallel for
# endif
# endif
for ( int i = 0 ; i < static_cast < int > ( pointCount ) ; + + i )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
const CCVector3 * P = corePoints . cloud - > getPoint ( i ) ;
2020-06-04 00:32:27 +02:00
CCCoreLib : : ReferenceCloud Yk ( & cloud2 ) ;
2019-03-23 20:42:01 +01:00
double maxSquareDist = 0 ;
2018-11-04 11:21:50 +01:00
2020-06-04 00:32:27 +02:00
ScalarType s = CCCoreLib : : NAN_VALUE ;
2018-11-04 11:21:50 +01:00
2019-03-23 20:42:01 +01:00
int neighborhoodSize = 0 ;
2019-03-25 18:45:28 +01:00
if ( octree - > findPointNeighbourhood ( P , & Yk , 1 , octreeLevel , maxSquareDist , 0.0 , & neighborhoodSize ) > = 1 )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
double s1 = field1 . pointValue ( corePoints . originIndex ( i ) ) ;
double s2 = field2 . pointValue ( Yk . getPointGlobalIndex ( 0 ) ) ;
s = masc : : Feature : : PerformMathOp ( s1 , s2 , op ) ;
2018-11-04 11:21:50 +01:00
}
2019-03-23 20:42:01 +01:00
outSF - > setValue ( i , s ) ;
2018-11-04 18:15:49 +01:00
2019-03-23 20:42:01 +01:00
if ( i & & ( i % tenth ) = = 0 )
{
double density = meanNeighborhoodSize / tenth ;
if ( density < 1.1 )
2018-11-04 18:15:49 +01:00
{
2020-06-04 00:32:27 +02:00
if ( octreeLevel + 1 < CCCoreLib : : DgmOctree : : MAX_OCTREE_LEVEL )
2019-03-23 20:42:01 +01:00
+ + octreeLevel ;
2018-11-04 18:15:49 +01:00
}
2019-03-23 20:42:01 +01:00
else while ( density > 2.9 )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
if ( octreeLevel < = 5 )
break ;
- - octreeLevel ;
density / = 2.0 ;
2018-11-04 11:21:50 +01:00
}
2019-03-23 20:42:01 +01:00
ccLog : : Print ( QString ( " [Adaptative octree level] Mean neighborhood size: %1 --> new level = %2 " ) . arg ( meanNeighborhoodSize / tenth ) . arg ( octreeLevel ) ) ;
meanNeighborhoodSize = 0 ;
2018-11-04 11:21:50 +01:00
}
2019-03-23 20:42:01 +01:00
else
2018-11-04 18:15:49 +01:00
{
2019-03-23 20:42:01 +01:00
meanNeighborhoodSize + = neighborhoodSize ;
2018-11-04 18:15:49 +01:00
}
2019-03-23 20:42:01 +01:00
if ( progressCb )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
mutex . lock ( ) ;
bool cancelled = ! nProgress . oneStep ( ) ;
mutex . unlock ( ) ;
if ( cancelled )
{
//process cancelled by the user
error = " Process cancelled " ;
break ;
}
2018-11-04 11:21:50 +01:00
}
2019-03-23 20:42:01 +01:00
}
2018-11-04 18:15:49 +01:00
2019-03-23 20:42:01 +01:00
outSF - > computeMinAndMax ( ) ;
2018-11-04 18:15:49 +01:00
2019-03-23 20:42:01 +01:00
if ( progressCb )
{
progressCb - > stop ( ) ;
2018-11-04 11:21:50 +01:00
}
2019-03-23 20:42:01 +01:00
return error . isEmpty ( ) ;
2018-11-04 11:21:50 +01:00
}
bool PointFeature : : prepare ( const CorePoints & corePoints ,
QString & error ,
2020-06-04 00:32:27 +02:00
CCCoreLib : : GenericProgressCallback * progressCb /*=nullptr*/ ,
2019-01-19 00:49:29 +01:00
SFCollector * generatedScalarFields /*=nullptr*/ )
2018-11-04 11:21:50 +01:00
{
if ( ! cloud1 | | ! corePoints . cloud )
{
//invalid input
assert ( false ) ;
2018-11-23 18:30:56 +01:00
error = " internal error (no input core points) " ;
2018-11-04 11:21:50 +01:00
return false ;
}
//look for the source field
2018-11-23 18:30:56 +01:00
assert ( ! field1 ) ;
field1 = retrieveField ( cloud1 , error ) ;
2018-11-04 11:21:50 +01:00
if ( ! field1 )
{
//error should be up to date
return false ;
}
2019-03-23 20:42:01 +01:00
assert ( ! field2 ) ;
if ( cloud2 )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
//no need to compute the second scalar field if no MATH operation has to be performed?!
if ( op ! = Feature : : NO_OPERATION )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
field2 = retrieveField ( cloud2 , error ) ;
if ( ! field2 )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
//error should be up to date
2018-12-01 22:40:04 +01:00
return false ;
2018-11-04 11:21:50 +01:00
}
}
2019-03-23 20:42:01 +01:00
else
{
assert ( false ) ;
error = " Feature has a second cloud associated but no MATH operation is defined " ;
return false ;
}
}
2018-11-04 11:21:50 +01:00
2019-03-23 20:42:01 +01:00
bool isScaled = scaled ( ) ;
//build the final SF name
2019-05-03 23:52:33 +02:00
QString resultSFName = field1 - > getName ( ) ;
if ( cloud2 | | corePoints . role ! = cloud1Label )
2019-03-23 20:42:01 +01:00
{
2019-05-03 23:52:33 +02:00
resultSFName + = " _ " + cloud1Label ;
2019-03-23 20:42:01 +01:00
}
2019-05-03 23:52:33 +02:00
2019-03-23 20:42:01 +01:00
if ( isScaled )
{
//shall we extract a statistical measure? (mandatory for scaled feature)
if ( stat = = Feature : : NO_STAT )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
assert ( false ) ;
error = " Scaled features (SCx) must have an associated STAT measure " ;
return false ;
2018-11-04 11:21:50 +01:00
}
2019-03-23 20:42:01 +01:00
resultSFName + = QString ( " _ " ) + Feature : : StatToString ( stat ) ;
}
else //not scaled
{
if ( cloud1 ! = corePoints . cloud & & cloud1 ! = corePoints . origin )
{
assert ( false ) ;
error = " Scale-less features (SC0) can only be defined on the core points (origin) cloud " ;
return false ;
}
}
2018-11-04 11:21:50 +01:00
2019-03-23 20:42:01 +01:00
if ( field2 & & op ! = Feature : : NO_OPERATION )
{
//include the math operation as well if necessary!
2019-05-03 23:52:33 +02:00
resultSFName + = " _ " + Feature : : OpToString ( op ) + " _ " + field2 - > getName ( ) + " _ " + cloud2Label ;
2019-03-23 20:42:01 +01:00
if ( isScaled )
{
assert ( stat ! = Feature : : NO_STAT ) ;
resultSFName + = QString ( " _ " ) + Feature : : StatToString ( stat ) ;
}
}
if ( isScaled )
{
resultSFName + = " @ " + QString : : number ( scale ) ;
2019-05-03 23:52:33 +02:00
//prepare the corresponding scalar field
statSF1 = PrepareSF ( corePoints . cloud , qPrintable ( resultSFName ) , generatedScalarFields , SFCollector : : CAN_REMOVE ) ;
if ( ! statSF1 )
{
error = QString ( " Failed to prepare scalar field for field '%1' @ scale %2 " ) . arg ( field1 - > getName ( ) ) . arg ( scale ) ;
return false ;
}
source . name = statSF1 - > getName ( ) ;
2019-03-23 20:42:01 +01:00
if ( field2 & & op ! = Feature : : NO_OPERATION )
2018-11-04 11:21:50 +01:00
{
2019-05-03 23:52:33 +02:00
QString resultSFName2 = field2 - > getName ( ) + QString ( " _ " ) + Feature : : StatToString ( stat ) + " _ " + cloud2Label + " @ " + QString : : number ( scale ) ;
2019-03-26 14:28:27 +01:00
//keepStatSF2 = (corePoints.cloud->getScalarFieldIndexByName(qPrintable(resultSFName2)) >= 0); //we remember that the scalar field was already existing!
2019-03-23 20:42:01 +01:00
2018-11-23 18:30:56 +01:00
assert ( ! statSF2 ) ;
2019-05-03 23:52:33 +02:00
statSF2 = PrepareSF ( corePoints . cloud , qPrintable ( resultSFName2 ) , generatedScalarFields , SFCollector : : ALWAYS_REMOVE ) ;
2018-11-04 11:21:50 +01:00
if ( ! statSF2 )
{
2018-12-01 22:40:04 +01:00
error = QString ( " Failed to prepare scalar field for field '%1' @ scale %2 " ) . arg ( field2 - > getName ( ) ) . arg ( scale ) ;
2018-11-04 11:21:50 +01:00
return false ;
}
}
2019-03-23 20:42:01 +01:00
2018-11-04 11:21:50 +01:00
return true ;
}
else //non scaled feature
{
2019-03-23 20:42:01 +01:00
assert ( cloud1 = = corePoints . cloud | | cloud1 = = corePoints . origin ) ;
2018-11-04 11:21:50 +01:00
//retrieve/create a SF to host the result
int sfIdx = corePoints . cloud - > getScalarFieldIndexByName ( qPrintable ( resultSFName ) ) ;
2019-03-23 20:42:01 +01:00
2020-06-04 00:32:27 +02:00
CCCoreLib : : ScalarField * resultSF = nullptr ;
2018-11-04 11:21:50 +01:00
if ( sfIdx > = 0 )
{
//reuse the existing field
resultSF = corePoints . cloud - > getScalarField ( sfIdx ) ;
}
else
{
//copy the SF1 field
resultSF = new ccScalarField ( qPrintable ( resultSFName ) ) ;
if ( ! resultSF - > resizeSafe ( corePoints . cloud - > size ( ) ) )
{
error = " Not enough memory " ;
resultSF - > release ( ) ;
return false ;
}
2019-03-23 20:42:01 +01:00
if ( op = = NO_OPERATION )
2018-11-04 11:21:50 +01:00
{
2019-03-23 20:42:01 +01:00
//simply copy the values
for ( unsigned i = 0 ; i < corePoints . size ( ) ; + + i )
{
resultSF - > setValue ( i , field1 - > pointValue ( corePoints . originIndex ( i ) ) ) ;
}
resultSF - > computeMinAndMax ( ) ;
}
else if ( field2 )
{
if ( ! ComputeMathOpWithNearestNeighbor ( corePoints ,
* field1 ,
resultSF ,
* cloud2 ,
* field2 ,
op ,
error ,
progressCb )
)
{
error = " Failed to perform the MATH operation ( " + error + " ) " ;
resultSF - > release ( ) ;
return false ;
}
2018-11-04 11:21:50 +01:00
}
2019-01-19 00:49:29 +01:00
2019-03-23 20:42:01 +01:00
int newSFIdx = corePoints . cloud - > addScalarField ( static_cast < ccScalarField * > ( resultSF ) ) ;
2019-01-19 00:49:29 +01:00
if ( generatedScalarFields )
{
//track the generated scalar-field
2019-05-03 23:52:33 +02:00
generatedScalarFields - > push ( corePoints . cloud , resultSF , SFCollector : : CAN_REMOVE ) ;
2019-01-19 00:49:29 +01:00
}
2019-03-23 20:42:01 +01:00
corePoints . cloud - > setCurrentDisplayedScalarField ( newSFIdx ) ;
2018-11-04 11:21:50 +01:00
}
2019-03-26 14:28:27 +01:00
source . name = resultSF - > getName ( ) ;
2018-11-04 11:21:50 +01:00
2018-11-23 18:30:56 +01:00
return true ;
}
}
2020-06-04 00:32:27 +02:00
bool PointFeature : : computeStat ( const CCCoreLib : : DgmOctree : : NeighboursSet & pointsInNeighbourhood , const IScalarFieldWrapper : : Shared & sourceField , double & outputValue ) const
2018-11-23 18:30:56 +01:00
{
outputValue = std : : numeric_limits < double > : : quiet_NaN ( ) ;
if ( ! sourceField | | stat = = Feature : : NO_STAT )
{
//invalid input parameters
assert ( false ) ;
return false ;
}
size_t kNN = pointsInNeighbourhood . size ( ) ;
if ( kNN = = 0 )
{
assert ( false ) ;
return false ;
}
//specific case
if ( stat = = Feature : : RANGE )
{
double minValue = 0 ;
double maxValue = 0 ;
for ( size_t k = 0 ; k < kNN ; + + k )
{
unsigned index = pointsInNeighbourhood [ k ] . pointIndex ;
double v = sourceField - > pointValue ( index ) ;
//track min and max values
if ( k ! = 0 )
{
if ( v < minValue )
minValue = v ;
else if ( v > maxValue )
maxValue = v ;
}
else
{
minValue = maxValue = v ;
}
}
outputValue = maxValue - minValue ;
return true ;
}
else
{
bool withSums = ( stat = = Feature : : MEAN | | stat = = Feature : : STD ) ;
2019-03-22 15:02:28 +01:00
bool storeValues = ( stat = = Feature : : MEDIAN | | stat = = Feature : : MODE | | stat = = Feature : : SKEW ) ;
2018-11-23 18:30:56 +01:00
double sum = 0.0 ;
double sum2 = 0.0 ;
2018-11-04 11:21:50 +01:00
2020-06-04 00:32:27 +02:00
CCCoreLib : : WeibullDistribution : : ScalarContainer values ;
2018-11-23 18:30:56 +01:00
if ( storeValues )
{
try
{
values . resize ( kNN ) ;
}
catch ( const std : : bad_alloc & )
{
ccLog : : Warning ( " Not enough memory " ) ;
return false ;
}
}
for ( unsigned k = 0 ; k < kNN ; + + k )
{
unsigned index = pointsInNeighbourhood [ k ] . pointIndex ;
double v = sourceField - > pointValue ( index ) ;
if ( withSums )
{
//compute average and std. dev.
sum + = v ;
sum2 + = v * v ;
}
if ( storeValues )
{
values [ k ] = static_cast < ScalarType > ( v ) ;
}
}
switch ( stat )
{
case Feature : : MEAN :
{
outputValue = sum / kNN ;
}
break ;
case Feature : : MODE :
{
2020-06-04 00:32:27 +02:00
CCCoreLib : : WeibullDistribution w ;
2019-03-26 14:28:27 +01:00
if ( w . computeParameters ( values ) )
outputValue = w . computeMode ( ) ;
2018-11-23 18:30:56 +01:00
}
break ;
2019-03-22 15:02:28 +01:00
case Feature : : MEDIAN :
{
size_t medianIndex = values . size ( ) / 2 ;
std : : nth_element ( values . begin ( ) , values . begin ( ) + medianIndex , values . end ( ) ) ;
outputValue = values [ medianIndex ] ;
}
break ;
2018-11-23 18:30:56 +01:00
case Feature : : STD :
{
outputValue = sqrt ( std : : abs ( sum2 * kNN - sum * sum ) ) / kNN ;
}
break ;
case Feature : : RANGE :
{
//we can't be here
assert ( false ) ;
}
return false ;
case Feature : : SKEW :
{
2020-06-04 00:32:27 +02:00
CCCoreLib : : WeibullDistribution w ;
2019-03-26 14:28:27 +01:00
if ( w . computeParameters ( values ) )
outputValue = w . computeSkewness ( ) ;
2018-11-23 18:30:56 +01:00
}
break ;
default :
{
ccLog : : Warning ( " Unhandled STAT measure " ) ;
assert ( false ) ;
}
return false ;
}
}
return true ;
}
bool PointFeature : : finish ( const CorePoints & corePoints , QString & error )
{
if ( ! scaled ( ) )
{
//nothing to do
2018-11-04 11:21:50 +01:00
return true ;
}
2018-11-23 18:30:56 +01:00
if ( ! corePoints . cloud )
{
//invalid input
assert ( false ) ;
error = " internal error (no input core points) " ;
return false ;
}
bool success = true ;
if ( statSF1 )
{
statSF1 - > computeMinAndMax ( ) ;
2018-12-01 14:39:11 +01:00
//update display
//if (corePoints.cloud->getDisplay())
{
int sfIndex1 = corePoints . cloud - > getScalarFieldIndexByName ( statSF1 - > getName ( ) ) ;
corePoints . cloud - > setCurrentDisplayedScalarField ( sfIndex1 ) ;
//corePoints.cloud->getDisplay()->redraw();
//QCoreApplication::processEvents();
}
2018-11-23 18:30:56 +01:00
}
if ( statSF2 )
{
//now perform the math operation
if ( op ! = Feature : : NO_OPERATION )
{
if ( ! PerformMathOp ( statSF1 , statSF2 , op ) )
{
error = " Failed to perform the MATH operation " ;
success = false ;
}
}
2019-03-26 14:28:27 +01:00
statSF2 - > computeMinAndMax ( ) ;
//DGM: we don't delete it now! As it could be used by other features!
//if (!keepStatSF2)
//{
// int sfIndex2 = corePoints.cloud->getScalarFieldIndexByName(statSF2->getName());
// if (sfIndex2 >= 0)
// {
// corePoints.cloud->deleteScalarField(sfIndex2);
// }
// else
// {
// assert(false);
// statSF2->release();
// }
// statSF2 = nullptr;
//}
2018-11-23 18:30:56 +01:00
}
return success ;
2018-11-04 11:21:50 +01:00
}
2018-11-04 19:23:11 +01:00
QString PointFeature : : toString ( ) const
2018-11-04 11:21:50 +01:00
{
2018-11-04 19:23:11 +01:00
//default keyword otherwise
QString description = ToString ( type ) ;
2018-11-04 11:21:50 +01:00
2018-11-04 19:23:11 +01:00
//special case for the 'SF' type
if ( type = = SF )
{
2019-02-08 18:09:26 +01:00
//'SF' + sf index
2018-11-04 19:23:11 +01:00
description + = QString : : number ( sourceSFIndex ) ;
}
2018-11-04 11:21:50 +01:00
2018-11-04 19:23:11 +01:00
if ( scaled ( ) )
{
description + = QString ( " _SC%1_%2 " ) . arg ( scale ) . arg ( StatToString ( stat ) ) ;
}
else
{
description + = " _SC0 " ;
}
description + = " _ " + cloud1Label ;
2018-12-01 14:39:11 +01:00
if ( cloud2 & & ! cloud2Label . isEmpty ( ) )
{
description + = " _ " + cloud2Label ;
if ( op ! = NO_OPERATION )
{
description + = " _ " + OpToString ( op ) ;
}
}
2018-11-04 19:23:11 +01:00
//Point features always have a scale equal to 0 by definition
return description ;
2018-11-04 11:21:50 +01:00
}