Files
ZXi 0e18ee5e2b Several updates on progress bar and early warning (#10)
* 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
2025-02-18 14:21:42 -07:00

249 lines
10 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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, 17241766. #
//# #
//# 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;
}
};