32 #include <QApplication>
33 #include <QElapsedTimer>
34 #include <QMessageBox>
35 #include <QtConcurrentMap>
65 return (
sX !=
nullptr &&
sY !=
nullptr &&
sZ !=
nullptr);
76 size_t count = set.size();
88 for (
size_t i = 0; i <
count; ++i) {
89 G.
x += set[i].point->x;
90 G.
y += set[i].point->y;
91 G.
z += set[i].point->z;
99 double minSquareDist = -1.0;
101 for (
size_t i = 0; i <
count; ++i) {
102 CCVector3d dG(G.
x - set[i].point->x, G.
y - set[i].point->y,
103 G.
z - set[i].point->z);
104 double squareDist = dG.
norm2();
105 if (minIndex < 0 || squareDist < minSquareDist) {
106 minSquareDist = squareDist;
107 minIndex =
static_cast<int>(i);
112 assert(minIndex >= 0);
113 unsigned pointIndex = set[minIndex].pointIndex;
197 double mean1 = 0, stdDev1 = 0;
198 bool validStats1 =
false;
211 size_t previousNeighbourCount = 0;
213 size_t neighbourCount =
215 ->getPointsInCylindricalNeighbourhoodProgressive(
217 if (neighbourCount != previousNeighbourCount) {
225 if (
fabs(mean1) + 2 * stdDev1 <
229 previousNeighbourCount = neighbourCount;
259 ScalarType val =
static_cast<ScalarType
>(stdDev1);
266 ScalarType val =
static_cast<ScalarType
>(n1);
274 double mean2 = 0, stdDev2 = 0;
275 bool validStats2 =
false;
288 size_t previousNeighbourCount = 0;
290 size_t neighbourCount =
292 ->getPointsInCylindricalNeighbourhoodProgressive(
294 if (neighbourCount != previousNeighbourCount) {
302 if (
fabs(mean2) + 2 * stdDev2 <
306 previousNeighbourCount = neighbourCount;
323 assert(stdDev2 != stdDev2 ||
344 dist =
static_cast<ScalarType
>(mean2 - mean1);
351 LODStdDev = stdDev1 * stdDev1 +
359 LODStdDev = (stdDev1 * stdDev1) / n1 +
360 (stdDev2 * stdDev2) / n2;
363 if (!std::isnan(LODStdDev)) {
366 ScalarType LOD =
static_cast<ScalarType
>(
367 1.96 * (sqrt(LODStdDev) +
376 bool significant = (
dist < -LOD ||
dist > LOD);
400 ScalarType val =
static_cast<ScalarType
>(stdDev2);
407 ScalarType val =
static_cast<ScalarType
>(n2);
429 QString& errorMessage,
432 QWidget* parentWidget ,
434 errorMessage.clear();
435 outputCloud =
nullptr;
441 if (!cloud1 || !cloud2) {
447 double normalScale = dlg.normalScaleDoubleSpinBox->value();
448 double projectionScale = dlg.cylDiameterDoubleSpinBox->value();
450 double samplingDist = dlg.cpSubsamplingDoubleSpinBox->value();
456 projectionScale / 2);
458 dlg.cylHalfHeightDoubleSpinBox->value());
461 dlg.rmsCheckBox->isChecked() ? dlg.rmsDoubleSpinBox->value() : 0.0;
467 !dlg.useSinglePass4DepthCheckBox->isChecked();
469 dlg.positiveSearchOnlyCheckBox->isChecked();
474 dlg.precisionMapsGroupBox->isEnabled() &&
475 dlg.precisionMapsGroupBox->isChecked();
478 QMessageBox::question(parentWidget,
"Precision Maps",
479 "Are you sure you want to compute the "
480 "M3C2 distances with precision maps?",
482 QMessageBox::No) == QMessageBox::No) {
484 dlg.precisionMapsGroupBox->setChecked(
false);
506 errorMessage =
"Invalid 'Precision maps' settings!";
519 QElapsedTimer initTimer;
531 errorMessage =
"Failed to compute cloud #1's octree!";
543 errorMessage =
"Failed to compute cloud #2's octree!";
551 bool corePointsHaveBeenSubsampled =
false;
571 QString(
"%1.subsampled [min dist. = %2]")
578 QString(
"[M3C2] Sub-sampled cloud has been saved "
584 corePointsHaveBeenSubsampled =
true;
586 errorMessage =
"Failed to compute sub-sampled core points!";
614 errorMessage =
"Not enough memory!";
624 bool normalsAreOk =
false;
625 bool useCorePointsOnly = dlg.normUseCorePointsCheckBox->isChecked();
629 outputName += QString(
" [HORIZONTAL]");
636 std::vector<PointCoordinateType>
radii;
639 double startScale = dlg.minScaleDoubleSpinBox->value();
640 double step = dlg.stepScaleDoubleSpinBox->value();
641 double stopScale = dlg.maxScaleDoubleSpinBox->value();
645 for (
double scale = startScale; scale <= stopScale;
651 outputName += QString(
" scale=[%1:%2:%3]")
657 normalScaleSF->
link();
660 outputName += QString(
" scale=%1").arg(
normalScale);
677 normalScaleSF, &pDlg, baseOctree);
684 "[M3C2] Some normals are invalid! You may have "
685 "to increase the scale.",
696 bool usePreferredOrientation =
697 dlg.normOriPreferredRadioButton->isChecked();
698 if (usePreferredOrientation) {
699 int preferredOrientation =
700 dlg.normOriPreferredComboBox->currentIndex();
701 assert(preferredOrientation >=
703 preferredOrientation <=
709 preferredOrientation))) {
711 "[M3C2] Failed to re-orient the normals "
712 "(invalid parameter?)";
724 maxThreadCount, &pDlg)) {
726 "[M3C2] Failed to re-orient the normals "
727 "with input point cloud!";
741 outputName += QString(
" scale=%1").arg(
normalScale);
768 outputName += QString(
" scale=%1").arg(
normalScale);
769 outputName += QString(
" [VERTICAL]");
777 errorMessage =
"Failed to compute normals!";
792 "Failed to allocate memory for core points normals!",
797 qint64 initTime_ms = initTimer.elapsed();
804 QString(
"[M3C2] Initialization & normal computation: %1 s.")
805 .arg(initTime_ms / 1000.0, 0,
'f', 3),
808 QElapsedTimer distCompTimer;
809 distCompTimer.start();
821 pDlg.
setInfo(QObject::tr(
"Core points: %1").arg(corePointCount));
830 errorMessage =
"Failed to allocate memory for distance values!";
841 "Failed to allocate memory for dist. uncertainty values!";
852 "Failed to allocate memory for change significance "
862 if (dlg.exportStdDevInfoCheckBox->isChecked()) {
863 QString prefix(
"STD");
878 "Failed to allocate memory for cloud #1 std. dev. "
893 "Failed to allocate memory for cloud #2 std. dev. "
900 if (dlg.exportDensityAtProjScaleCheckBox->isChecked()) {
909 "Failed to allocate memory for cloud #1 density "
923 "Failed to allocate memory for cloud #2 density "
936 ->findBestLevelForAGivenNeighbourhoodSizeExtraction(
944 QString(
"[M3C2] Working subdivision level (cloud #1): %1")
950 ->findBestLevelForAGivenNeighbourhoodSizeExtraction(
958 QString(
"[M3C2] Working subdivision level (cloud #2): %1")
974 "Failed to allocate memory for exporting normals!",
983 std::vector<unsigned> pointIndexes;
984 bool useParallelStrategy =
true;
986 useParallelStrategy =
false;
988 if (useParallelStrategy) {
990 pointIndexes.resize(corePointCount);
991 }
catch (
const std::bad_alloc&) {
993 useParallelStrategy =
false;
997 if (useParallelStrategy) {
998 for (
unsigned i = 0; i < corePointCount; ++i) {
1002 if (maxThreadCount == 0) {
1003 maxThreadCount = QThread::idealThreadCount();
1005 assert(maxThreadCount > 0 &&
1006 maxThreadCount <= QThread::idealThreadCount());
1007 QThreadPool::globalInstance()->setMaxThreadCount(
1009 QtConcurrent::blockingMap(pointIndexes,
1013 for (
unsigned i = 0; i < corePointCount; ++i) {
1020 errorMessage =
"Process canceled by user!";
1023 qint64 distTime_ms = distCompTimer.elapsed();
1027 QString(
"[M3C2] Distances computation: %1 s.")
1028 .arg(
static_cast<double>(distTime_ms) / 1000.0,
1046 if (normalScaleSF) {
1162 if (normalScaleSF) normalScaleSF->
release();
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
float PointCoordinateType
Type of the coordinates of a (N-D) point.
virtual void link()
Increase counter.
virtual void release()
Decrease counter and deletes object when 0.
Array of compressed 3D normals (single index)
Type norm2() const
Returns vector square norm.
Type norm() const
Returns vector norm.
Type & getValue(size_t index)
unsigned currentSize() const
virtual void setVisible(bool state)
Sets entity visibility.
virtual void showNormals(bool state)
Sets normals visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
virtual ccOctree::Shared computeOctree(cloudViewer::GenericProgressCallback *progressCb=nullptr, bool autoAddChild=true)
Computes the cloud octree.
virtual ccOctreeProxy * getOctreeProxy() const
Returns the associated octree proxy (if any)
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
void importParametersFrom(const ccGenericPointCloud *cloud)
Imports the parameters from another cloud.
ccHObject * getParent() const
Returns parent object.
static bool UpdateNormalOrientations(ccGenericPointCloud *theCloud, NormsIndexesTableType &theNormsCodes, Orientation preferredOrientation)
Updates normals orientation based on a preferred orientation.
static const CCVector3 & GetNormal(unsigned normIndex)
Static access to ccNormalVectors::getNormal.
Orientation
'Default' orientations
@ PLUS_X
N.x always positive.
virtual QString getName() const
Returns object name.
virtual void setName(const QString &name)
Sets object name.
virtual void setEnabled(bool state)
Sets the "enabled" property.
QSharedPointer< ccOctree > Shared
Shared pointer.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
bool resizeTheNormsTable()
Resizes the compressed normals array.
void setPointNormalIndex(size_t pointIndex, CompressedNormType norm)
Sets a particular point compressed normal.
void setNormsTable(NormsIndexesTableType *norms)
Sets the (compressed) normals table.
NormsIndexesTableType * normals() const
Returns pointer on compressed normals indexes table.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool hasNormals() const override
Returns whether normals are enabled or not.
void invalidateBoundingBox() override
Invalidates bounding box.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
void deleteScalarField(int index) override
Deletes a specific scalar field.
void setPointNormal(size_t pointIndex, const CCVector3 &N)
Sets a particular point normal (shortcut)
A scalar field associated to display-related parameters.
void setMinDisplayed(ScalarType val)
Sets the minimum displayed value.
void setSymmetricalScale(bool state)
Sets whether the color scale should be symmetrical or not.
void computeMinAndMax() override
Determines the min and max values.
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
virtual unsigned size() const =0
Returns the number of points.
bool oneStep()
Increments total progress value of a single unit.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
unsigned size() const override
const CCVector3 * getPoint(unsigned index) const override
A very simple point cloud (no point duplication)
A simple scalar field (to be associated to a point cloud)
ScalarType & getValue(std::size_t index)
void setValue(std::size_t index, ScalarType value)
const char * getName() const
Returns scalar field name.
bool resizeSafe(std::size_t count, bool initNewElements=false, ScalarType valueForNewElements=0)
Resizes memory (no exception thrown)
Main application interface (for plugins)
virtual void refreshAll(bool only2D=false, bool forceRedraw=true)=0
Redraws all GL windows that have the 'refresh' flag on.
virtual void addToDB(ccHObject *obj, bool updateZoom=false, bool autoExpandDBTree=true, bool checkDimensions=false, bool autoRedraw=true)=0
virtual void dispToConsole(QString message, ConsoleMessageLevel level=STD_CONSOLE_MESSAGE)=0
Graphical progress indicator (thread-safe)
virtual void start() override
virtual void setInfo(const char *infoStr) override
Notifies some information about the ongoing process.
virtual void setMethodTitle(const char *methodTitle) override
Notifies the algorithm title.
M3C2 plugin's main dialog.
qM3C2Normals::ComputationMode getNormalsComputationMode() const
Returns selected normals computation mode.
unsigned getMinPointsForStats(unsigned defaultValue=5) const
ExportOptions
Exportation options.
ccPointCloud * getNormalsOrientationCloud() const
Returns the cloud to be used for normals orientation (if any)
ExportOptions getExportOption() const
Returns selected export option.
ccPointCloud * getCloud2() const
Returns cloud #2.
ccPointCloud * getCloud1() const
Returns cloud #1.
ccPointCloud * getCorePointsCloud() const
Get core points cloud (if any)
int getMaxThreadCount() const
Returns the max number of threads to use.
bool keepOriginalCloud() const
ComputationMode
Normals computation mode.
@ USE_CORE_POINTS_NORMALS
static void MakeNormalsHorizontal(NormsIndexesTableType &normsCodes)
Makes all normals horizontal.
static bool ComputeCorePointsNormals(cloudViewer::GenericIndexedCloud *corePoints, NormsIndexesTableType *corePointsNormals, ccGenericPointCloud *sourceCloud, const std::vector< PointCoordinateType > &sortedRadii, bool &invalidNormals, int maxThreadCount=0, ccScalarField *normalScale=nullptr, cloudViewer::GenericProgressCallback *progressCb=nullptr, cloudViewer::DgmOctree *inputOctree=nullptr)
Computes normals on core points only.
static bool UpdateNormalOrientationsWithCloud(cloudViewer::GenericIndexedCloud *normCloud, NormsIndexesTableType &normsCodes, cloudViewer::GenericIndexedCloud *orientationCloud, int maxThreadCount=0, cloudViewer::GenericProgressCallback *progressCb=nullptr)
static bool Compute(const qM3C2Dialog &dlg, QString &errorMessage, ccPointCloud *&outputCloud, bool allowDialogs, QWidget *parentWidget=nullptr, ecvMainAppInterface *app=nullptr)
__host__ __device__ float2 fabs(float2 v)
static double dist(double x1, double y1, double x2, double y2)
static void error(char *msg)
static const char DENSITY_CLOUD2_SF_NAME[]
static ScalarType SCALAR_ONE
void ComputeM3C2DistForPoint(unsigned index)
static const char SIG_CHANGE_SF_NAME[]
static const char STD_DEV_CLOUD2_SF_NAME[]
static double ComputePMUncertainty(cloudViewer::DgmOctree::NeighboursSet &set, const CCVector3 &N, const PrecisionMaps &PM)
static const char STD_DEV_CLOUD1_SF_NAME[]
static const char DENSITY_CLOUD1_SF_NAME[]
static const char NORMAL_SCALE_SF_NAME[]
static const char M3C2_DIST_SF_NAME[]
Default name for M3C2 scalar fields.
static ScalarType SCALAR_ZERO
static void RemoveScalarField(ccPointCloud *cloud, const char sfName[])
static M3C2Params s_M3C2Params
static const char DIST_UNCERTAINTY_SF_NAME[]
ccPointCloud * outputCloud
NormsIndexesTableType * coreNormals
ccOctree::Shared cloud2Octree
ccScalarField * distUncertaintySF
ccScalarField * densityCloud1SF
qM3C2Dialog::ExportOptions exportOption
PointCoordinateType projectionDepth
ccScalarField * m3c2DistSF
cloudViewer::NormalizedProgress * nProgress
PointCoordinateType projectionRadius
ccScalarField * densityCloud2SF
ccScalarField * stdDevCloud1SF
ccScalarField * sigChangeSF
ccOctree::Shared cloud1Octree
ccScalarField * stdDevCloud2SF
ccPointCloud * corePoints
cloudViewer::ScalarField * sY
cloudViewer::ScalarField * sX
cloudViewer::ScalarField * sZ
unsigned char level
subdivision level at which to apply the extraction process
PointCoordinateType maxHalfLength
Cylinder (half) length.
NeighboursSet neighbours
Neighbour points falling inside the cylinder.
CCVector3 center
Cylinder center.
PointCoordinateType radius
Cylinder radius.
CCVector3 dir
Cylinder axis (direction)
PointCoordinateType currentHalfLength
Current search depth.