mirror of
https://github.com/truebelief/cc-treeiso-plugin.git
synced 2026-08-29 08:34:29 +08:00
0e18ee5e2b
* Major upgrade of dependencies and running time 1. Removed the Boost dependency by adopting a newer cut-pursuit version from the authors (resulting in a 5–15× speed improvement). 2. Fixed an issue with the nearest neighbor search caused by an incorrect 2D array order input to the knn_cpp query function. 3. Enabled customization of the number of threads for knn-cpp, yielding a slight speed improvement. 4. Optimized array calculations through enhanced use of STL algorithms. * Change log added * Smoothened the progress bar and added ground detectioned - Improved progress bar increment - Added rapid detection of ground points based on quantile distribution - Added warning of ground points before continuing initial segmentation * Crash fix - Avoided crashes by adding error handling once the maximum component limit is reached - Improved the speed of unique and get_subset functions
249 lines
10 KiB
C++
249 lines
10 KiB
C++
#pragma once
|
||
|
||
//#######################################################################################
|
||
//# #
|
||
//# CLOUDCOMPARE PLUGIN: qTreeIso #
|
||
//# #
|
||
//# 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. #
|
||
//# #
|
||
//# Please cite the following paper if you find this tool helpful #
|
||
//# #
|
||
//# Xi, Z.; Hopkinson, C. 3D Graph-Based Individual-Tree Isolation (Treeiso) #
|
||
//# from Terrestrial Laser Scanning Point Clouds. Remote Sens. 2022, 14, 6116. #
|
||
//# https://doi.org/10.3390/rs14236116 #
|
||
//# #
|
||
//# Our work relies on the cut-pursuit algorithm, please also consider citing: #
|
||
//# Landrieu, L.; Obozinski, G. Cut Pursuit: Fast Algorithms to Learn Piecewise #
|
||
//# Constant Functions on General Weighted Graphs. SIAM J. Imaging Sci. #
|
||
//# 2017, 10, 1724–1766. #
|
||
//# #
|
||
//# Copyright © #
|
||
//# Artemis Lab, Department of Geography & Environment #
|
||
//# University of Lethbridge, Canada #
|
||
//# #
|
||
//# #
|
||
//# Zhouxin Xi and Chris Hopkinson; #
|
||
//# truebelief2010@gmail.com; c.hopkinson@uleth.ca #
|
||
//# #
|
||
//#######################################################################################
|
||
|
||
// Matlab and python versions shared via:
|
||
// https://github.com/truebelief/artemis_treeiso
|
||
|
||
//CloudCompare
|
||
#include <ccCommandLineInterface.h>
|
||
|
||
//Local
|
||
#include "ccTreeIsoDlg.h"
|
||
#include "qTreeIso.h"
|
||
#include "TreeIso.h"
|
||
|
||
static const char COMMAND_TREEISO[] = "TREEISO";
|
||
|
||
static const char COMMAND_LAMBDA1[] = "LAMBDA1";
|
||
static const char COMMAND_K1[] = "K1";
|
||
static const char COMMAND_DECIMATE_RESOLUTION1[] = "DECIMATE_RESOLUTION1";
|
||
|
||
static const char COMMAND_LAMBDA2[] = "LAMBDA2";
|
||
static const char COMMAND_K2[] = "K2";
|
||
static const char COMMAND_MAX_GAP[] = "MAX_GAP";
|
||
static const char COMMAND_DECIMATE_RESOLUTION2[] = "DECIMATE_RESOLUTION2";
|
||
|
||
static const char COMMAND_RHO[] = "RHO";
|
||
static const char COMMAND_VERTICAL_OVERLAP_WEIGHT[] = "VERTICAL_OVERLAP_WEIGHT";
|
||
|
||
//! qTreeIso command line processor
|
||
struct CommandTreeIso : public ccCommandLineInterface::Command
|
||
{
|
||
CommandTreeIso() : ccCommandLineInterface::Command("TREEISO", COMMAND_TREEISO) {}
|
||
|
||
bool process(ccCommandLineInterface& cmd) override
|
||
{
|
||
cmd.print("[TreeIso]");
|
||
|
||
if (cmd.clouds().empty())
|
||
{
|
||
cmd.error("No cloud loaded");
|
||
return false;
|
||
}
|
||
|
||
//initial parameters
|
||
qTreeIso::Parameters parameters;
|
||
|
||
bool try_init_seg = false;
|
||
bool try_intermediate_seg = false;
|
||
bool try_final_seg = false;
|
||
|
||
while (!cmd.arguments().empty())
|
||
{
|
||
const QString& ARGUMENT = cmd.arguments().front();
|
||
if ((ccCommandLineInterface::IsCommand(ARGUMENT, COMMAND_LAMBDA1)))
|
||
{
|
||
try_init_seg = true;
|
||
cmd.arguments().pop_front();
|
||
bool convert = false;
|
||
|
||
parameters.reg_strength1 = cmd.arguments().takeFirst().toFloat(&convert);
|
||
if ((!convert) & (parameters.reg_strength1 <= 0.0))
|
||
{
|
||
return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_LAMBDA1));
|
||
}
|
||
cmd.print(QString("lambda1 (Regularization strength for initial segmentation) set: %1").arg(parameters.reg_strength1));
|
||
}
|
||
else if (ccCommandLineInterface::IsCommand(ARGUMENT, COMMAND_K1))
|
||
{
|
||
try_init_seg = true;
|
||
cmd.arguments().pop_front();
|
||
bool convert = false;
|
||
|
||
parameters.min_nn1 = cmd.arguments().takeFirst().toInt(&convert);
|
||
if ((!convert) & (parameters.min_nn1 < 3))
|
||
{
|
||
return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_K1));
|
||
}
|
||
cmd.print(QString("K1 (Nearest neighbors to search for initial segmentation) set: %1").arg(parameters.min_nn1));
|
||
}
|
||
else if (ccCommandLineInterface::IsCommand(ARGUMENT, COMMAND_DECIMATE_RESOLUTION1))
|
||
{
|
||
try_init_seg = true;
|
||
cmd.arguments().pop_front();
|
||
bool convert = false;
|
||
|
||
parameters.decimate_res1 = cmd.arguments().takeFirst().toFloat(&convert);
|
||
if ((!convert) & (parameters.decimate_res1 < 0.001))
|
||
{
|
||
return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_DECIMATE_RESOLUTION1));
|
||
}
|
||
cmd.print(QString("Decimated resolution (in m) for initial segmentation set: %1").arg(parameters.decimate_res1));
|
||
}
|
||
else if (ccCommandLineInterface::IsCommand(ARGUMENT, COMMAND_LAMBDA2))
|
||
{
|
||
try_intermediate_seg = true;
|
||
cmd.arguments().pop_front();
|
||
bool convert = false;
|
||
|
||
parameters.reg_strength2 = cmd.arguments().takeFirst().toFloat(&convert);
|
||
if ((!convert) & (parameters.reg_strength2 <= 0.0))
|
||
{
|
||
return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_LAMBDA2));
|
||
}
|
||
cmd.print(QString("lambda2 (Regularization strength for intermediate segmentation) set: %1").arg(parameters.reg_strength2));
|
||
}
|
||
else if (ccCommandLineInterface::IsCommand(ARGUMENT, COMMAND_K2))
|
||
{
|
||
try_intermediate_seg = true;
|
||
cmd.arguments().pop_front();
|
||
bool convert = false;
|
||
|
||
parameters.min_nn2 = cmd.arguments().takeFirst().toInt(&convert);
|
||
if ((!convert) & (parameters.min_nn2 < 3))
|
||
{
|
||
return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_K2));
|
||
}
|
||
cmd.print(QString("K2 (Nearest neighbors to search for intermediate segmentation) set: %1").arg(parameters.min_nn2));
|
||
}
|
||
else if (ccCommandLineInterface::IsCommand(ARGUMENT, COMMAND_MAX_GAP))
|
||
{
|
||
try_intermediate_seg = true;
|
||
cmd.arguments().pop_front();
|
||
bool convert = false;
|
||
|
||
parameters.max_gap = cmd.arguments().takeFirst().toFloat(&convert);
|
||
if ((!convert) & (parameters.max_gap <= 0.0001))
|
||
{
|
||
return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_MAX_GAP));
|
||
}
|
||
cmd.print(QString("Maximum point gap (in m) for intermediate segmentation set: %1").arg(parameters.max_gap));
|
||
}
|
||
else if (ccCommandLineInterface::IsCommand(ARGUMENT, COMMAND_DECIMATE_RESOLUTION2))
|
||
{
|
||
try_intermediate_seg = true;
|
||
cmd.arguments().pop_front();
|
||
bool convert = false;
|
||
|
||
parameters.decimate_res2 = cmd.arguments().takeFirst().toFloat(&convert);
|
||
if ((!convert) & (parameters.decimate_res2 < 0.001))
|
||
{
|
||
return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_DECIMATE_RESOLUTION2));
|
||
}
|
||
cmd.print(QString("Decimated resolution (in m) for intermediate segmentation set: %1").arg(parameters.decimate_res2));
|
||
}
|
||
else if (ccCommandLineInterface::IsCommand(ARGUMENT, COMMAND_RHO))
|
||
{
|
||
try_final_seg = true;
|
||
cmd.arguments().pop_front();
|
||
bool convert = false;
|
||
|
||
parameters.rel_height_length_ratio = cmd.arguments().takeFirst().toFloat(&convert);
|
||
if ((!convert) & (parameters.rel_height_length_ratio < 0.001))
|
||
{
|
||
return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_RHO));
|
||
}
|
||
cmd.print(QString("Relative height to length ratio (used to detect non-stems for final segmentation) set: %1").arg(parameters.rel_height_length_ratio));
|
||
|
||
}
|
||
else if (ccCommandLineInterface::IsCommand(ARGUMENT, COMMAND_VERTICAL_OVERLAP_WEIGHT))
|
||
{
|
||
try_final_seg = true;
|
||
cmd.arguments().pop_front();
|
||
bool convert = false;
|
||
|
||
parameters.vertical_weight = cmd.arguments().takeFirst().toFloat(&convert);
|
||
if ((!convert) & (parameters.vertical_weight < 0.001))
|
||
{
|
||
return cmd.error(QObject::tr("Invalid parameter: value after \"-%1\"").arg(COMMAND_VERTICAL_OVERLAP_WEIGHT));
|
||
}
|
||
cmd.print(QString("Vertical overlapping ratio weight for final segmentation set: %1").arg(parameters.vertical_weight));
|
||
}
|
||
else
|
||
{
|
||
cmd.print("Parameters All Set");
|
||
break;
|
||
}
|
||
}
|
||
|
||
for (CLCloudDesc& desc : cmd.clouds())
|
||
{
|
||
//Convert CC point cloud to treeIso type
|
||
unsigned count = desc.pc->size();
|
||
if (count == 0)
|
||
{
|
||
cmd.print(QString("Cloud %1 is empty").arg(desc.pc->getName()));
|
||
continue;
|
||
}
|
||
|
||
if (try_init_seg)
|
||
{
|
||
if (!TreeIso::Init_seg_pcd(desc.pc, parameters.min_nn1, parameters.reg_strength1, parameters.decimate_res1, parameters.threads1, [](int) {/*No implementation for progress bar*/}))
|
||
{
|
||
return cmd.error("Failed to finish initial segmentation due to unknown reasons.");
|
||
}
|
||
}
|
||
if (try_intermediate_seg)
|
||
{
|
||
if (!TreeIso::Intermediate_seg_pcd(desc.pc, parameters.min_nn2, parameters.reg_strength2, parameters.decimate_res2, parameters.max_gap, parameters.threads2, [](int) {/*No implementation for progress bar*/}))
|
||
{
|
||
return cmd.error("Failed to finish intermediate segmentation due to unknown reasons.");
|
||
}
|
||
}
|
||
if (try_final_seg)
|
||
{
|
||
if (!TreeIso::Final_seg_pcd(desc.pc, parameters.min_nn2, parameters.rel_height_length_ratio, parameters.vertical_weight, [](int) {/*No implementation for progress bar*/}))
|
||
{
|
||
return cmd.error("Failed to finish final segmentation due to unknown reasons.");
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
};
|
||
|