Skip to content

Commit

Permalink
add command-line option to save the reconstructed skeletons into PLY …
Browse files Browse the repository at this point in the history
…files
  • Loading branch information
LiangliangNan committed Feb 10, 2023
1 parent a6f5e13 commit 5d4cd57
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 22 deletions.
99 changes: 86 additions & 13 deletions AdTree/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,56 @@

using namespace easy3d;

// save the smoothed skeleton into a PLY file (where each vertex has a radius)
void save_skeleton(Skeleton* skeleton, PointCloud* cloud, const std::string& file_name) {
const ::Graph& sgraph = skeleton->get_smoothed_skeleton();
if (boost::num_edges(sgraph) == 0) {
std::cerr << "failed to save skeleton (no edge exists)" << std::endl;
return;
}

// convert the boost graph to Graph (avoid modifying easy3d's GraphIO, or writing IO for boost graph)

std::unordered_map<SGraphVertexDescriptor, easy3d::Graph::Vertex> vvmap;
easy3d::Graph g;

auto vertexRadius = g.add_vertex_property<float>("v:radius");
auto vts = boost::vertices(sgraph);
for (SGraphVertexIterator iter = vts.first; iter != vts.second; ++iter) {
SGraphVertexDescriptor vd = *iter;
if (boost::degree(vd, sgraph) != 0) { // ignore isolated vertices
const vec3& vp = sgraph[vd].cVert;
auto v = g.add_vertex(vp);
vertexRadius[v] = sgraph[vd].radius;
vvmap[vd] = v;
}
}

auto egs = boost::edges(sgraph);
for (SGraphEdgeIterator iter = egs.first; iter != egs.second; ++iter) {
SGraphEdgeDescriptor ed = *iter; // the edge descriptor
SGraphEdgeProp ep = sgraph[ed]; // the edge property

SGraphVertexDescriptor s = boost::source(*iter, sgraph);
SGraphVertexDescriptor t = boost::target(*iter, sgraph);
g.add_edge(vvmap[s], vvmap[t]);
}

auto offset = cloud->get_model_property<dvec3>("translation");
if (offset) {
auto prop = g.model_property<dvec3>("translation");
prop[0] = offset[0];
}

if (GraphIO::save(file_name, &g))
std::cout << "model of skeletons saved to: " << file_name << std::endl;
else
std::cerr << "failed to save the model of skeletons into file" << std::endl;
}


// returns the number of processed input files.
int batch_reconstruct(std::vector<std::string>& point_cloud_files, const std::string& output_folder) {
int batch_reconstruct(std::vector<std::string>& point_cloud_files, const std::string& output_folder, bool export_skeleton) {
int count(0);
for (std::size_t i=0; i<point_cloud_files.size(); ++i) {
const std::string& xyz_file = point_cloud_files[i];
Expand Down Expand Up @@ -111,7 +159,12 @@ int batch_reconstruct(std::vector<std::string>& point_cloud_files, const std::st
++count;
}
else
std::cerr << "failed in saving the model of branches" << std::endl;
std::cerr << "failed to save the model of branches" << std::endl;

if (export_skeleton) {
const std::string& skeleton_file = output_folder + "/" + file_system::base_name(cloud->name()) + "_skeleton.ply";
save_skeleton(skeleton, cloud, skeleton_file);
}

delete cloud;
delete mesh;
Expand All @@ -123,15 +176,30 @@ int batch_reconstruct(std::vector<std::string>& point_cloud_files, const std::st


int main(int argc, char *argv[]) {
// argc = 3;
// argc = 2;
// argv[1] = "/Users/lnan/Projects/adtree/data";
// argv[2] = "/Users/lnan/Projects/adtree/data-results";

if (argc == 1) {
TreeViewer viewer;
viewer.run();
return EXIT_SUCCESS;
} else if (argc == 3) {
} else if (argc >= 3) {
bool export_skeleton = false;
for (int i = 0; i < argc; ++i) {
if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "-skeleton") == 0) {
export_skeleton = true;
break;
}
}

if (export_skeleton) {
std::cout << "You have requested to save the reconstructed tree skeleton(s) in PLY format into the output directory." << std::endl;
std::cout << "The skeleton file(s) can be visualized using Easy3D: https://github.com/LiangliangNan/Easy3D" << std::endl;
}
else
std::cout << "Tree skeleton(s) will not be saved (append '-s' or '-skeleton' in commandline to enable it)" << std::endl;

std::string first_arg(argv[1]);
std::string second_arg(argv[2]);
if (file_system::is_file(second_arg))
Expand All @@ -140,7 +208,7 @@ int main(int argc, char *argv[]) {
std::string output_dir = second_arg;
if (file_system::is_file(first_arg)) {
std::vector<std::string> cloud_files = {first_arg};
return batch_reconstruct(cloud_files, output_dir) > 0;
return batch_reconstruct(cloud_files, output_dir, export_skeleton) > 0;
} else if (file_system::is_directory(first_arg)) {
std::vector<std::string> entries;
file_system::get_directory_entries(first_arg, entries, false);
Expand All @@ -149,7 +217,7 @@ int main(int argc, char *argv[]) {
if (file_name.size() > 3 && file_name.substr(file_name.size() - 3) == "xyz")
cloud_files.push_back(first_arg + "/" + file_name);
}
return batch_reconstruct(cloud_files, output_dir) > 0;
return batch_reconstruct(cloud_files, output_dir, export_skeleton) > 0;
} else
std::cerr
<< "WARNING: unknown first argument (expecting either a point cloud file in *.xyz format or a\n"
Expand All @@ -158,13 +226,18 @@ int main(int argc, char *argv[]) {
}

std::cerr << "Usage: AdTree can be run in three modes, which can be selected based on arguments:" << std::endl;
std::cerr << " - GUI mode." << std::endl;
std::cerr << " Command: ./AdTree" << std::endl;
std::cerr << " - Single processing mode (i.e., processing a single point cloud file)." << std::endl;
std::cerr << " Command: ./AdTree <xyz_file_path> <output_directory>" << std::endl;
std::cerr << " - Batch processing mode (i.e., all *.xyz files in the input directory will be treated as input \n";
std::cerr << " for reconstruction and the reconstructed models will be save in the output directory).\n";
std::cerr << " Command: ./AdTree <xyz_files_directory> <output_directory>" << std::endl;
std::cerr << " 1) GUI mode." << std::endl;
std::cerr << " Command: ./AdTree" << std::endl << std::endl;
std::cerr << " 2) Commandline single processing mode (i.e., processing a single point cloud file)." << std::endl;
std::cerr << " Command: ./AdTree <xyz_file_path> <output_directory> [-s|-skeleton]" << std::endl;
std::cerr << " - <xyz_file_path>: a mandatory argument specifying the path to the input point cloud file" << std::endl;
std::cerr << " - <output_directory>: a mandatory argument specifying where to save the results" << std::endl;
std::cerr << " - [-s] or [-skeleton]: also export the skeletons (omit this argument it if you don't need skeletons)" << std::endl << std::endl;
std::cerr << " 3) Commandline batch processing mode (i.e., all *.xyz files in an input directory will be processed)." << std::endl;
std::cerr << " Command: ./AdTree <xyz_files_directory> <output_directory> [-s|-skeleton]" << std::endl;
std::cerr << " - <xyz_files_directory>: a mandatory argument specifying the directory containing the input point cloud files" << std::endl;
std::cerr << " - <output_directory>: a mandatory argument specifying where to save the results" << std::endl;
std::cerr << " - [-s] or [-skeleton]: also export the skeletons (omit this argument it if you don't need skeletons)" << std::endl << std::endl;

return EXIT_FAILURE;
}
2 changes: 1 addition & 1 deletion AdTree/tree_viewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ void TreeViewer::export_skeleton() const {
auto e = g.add_edge(vvmap[s], vvmap[t]);
edgeRadius[e] = skeleton[*iter].nRadius;
}
#else // save the smoothed skeleton, for which each very has a radius.
#else // save the smoothed skeleton into a PLY file (where each vertex has a radius)
const ::Graph& skeleton = skeleton_->get_smoothed_skeleton();
if (boost::num_edges(skeleton) == 0) {
std::cerr << "skeleton has 0 edges" << std::endl;
Expand Down
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,26 @@ Don't have any experience with C/C++ programming? Have a look at [How to build A

After obtaining the executable, AdTree can be run in three modes, which can be selected based
on arguments.
- GUI mode that provides a user interface with menus. You can double-click to run the app or from the command
using `./AdTree`.

- Single processing mode (i.e., processing a single point cloud file) from the command line using
- GUI mode. It provides a user interface with menus. You can double-click the app or run it from the commandline
```
./AdTree
```
./AdTree <xyz_file_path> <output_directory>

- Commandline single processing mode (i.e., processing a single point cloud file).
```
- Batch processing mode (i.e., all *.xyz files in the input directory will be treated as input and the reconstructed
models will be saved in the output directory) from the command line using
./AdTree <xyz_file_path> <output_directory> [-s|-skeleton]
```
- `<xyz_file_path>`: a mandatory argument specifying the path to the input point cloud file
- `<output_directory>`: a mandatory argument specifying where to save the results
- `[-s]` or `[-skeleton]`: also export the skeletons (omit this argument it if you don't need skeletons)

- Commandline batch processing mode (i.e., all *.xyz files in an input directory will be processed).
```
./AdTree <xyz_files_directory> <output_directory>
./AdTree <xyz_files_directory> <output_directory> [-s|-skeleton]
```
- `<xyz_files_directory>`: a mandatory argument specifying the directory containing the input point cloud files
- `<output_directory>`: a mandatory argument specifying where to save the results
- `[-s]` or `[-skeleton]`: also export the skeletons (omit this argument it if you don't need skeletons)

<p align="center">
<img src="./resources/images/ui.jpg" width="600">
Expand Down

0 comments on commit 5d4cd57

Please sign in to comment.