4444
4545using namespace easy3d ;
4646
47+ // save the smoothed skeleton into a PLY file (where each vertex has a radius)
48+ void save_skeleton (Skeleton* skeleton, PointCloud* cloud, const std::string& file_name) {
49+ const ::Graph& sgraph = skeleton->get_smoothed_skeleton ();
50+ if (boost::num_edges (sgraph) == 0 ) {
51+ std::cerr << " failed to save skeleton (no edge exists)" << std::endl;
52+ return ;
53+ }
54+
55+ // convert the boost graph to Graph (avoid modifying easy3d's GraphIO, or writing IO for boost graph)
56+
57+ std::unordered_map<SGraphVertexDescriptor, easy3d::Graph::Vertex> vvmap;
58+ easy3d::Graph g;
59+
60+ auto vertexRadius = g.add_vertex_property <float >(" v:radius" );
61+ auto vts = boost::vertices (sgraph);
62+ for (SGraphVertexIterator iter = vts.first ; iter != vts.second ; ++iter) {
63+ SGraphVertexDescriptor vd = *iter;
64+ if (boost::degree (vd, sgraph) != 0 ) { // ignore isolated vertices
65+ const vec3& vp = sgraph[vd].cVert ;
66+ auto v = g.add_vertex (vp);
67+ vertexRadius[v] = sgraph[vd].radius ;
68+ vvmap[vd] = v;
69+ }
70+ }
71+
72+ auto egs = boost::edges (sgraph);
73+ for (SGraphEdgeIterator iter = egs.first ; iter != egs.second ; ++iter) {
74+ SGraphEdgeDescriptor ed = *iter; // the edge descriptor
75+ SGraphEdgeProp ep = sgraph[ed]; // the edge property
76+
77+ SGraphVertexDescriptor s = boost::source (*iter, sgraph);
78+ SGraphVertexDescriptor t = boost::target (*iter, sgraph);
79+ g.add_edge (vvmap[s], vvmap[t]);
80+ }
81+
82+ auto offset = cloud->get_model_property <dvec3>(" translation" );
83+ if (offset) {
84+ auto prop = g.model_property <dvec3>(" translation" );
85+ prop[0 ] = offset[0 ];
86+ }
87+
88+ if (GraphIO::save (file_name, &g))
89+ std::cout << " model of skeletons saved to: " << file_name << std::endl;
90+ else
91+ std::cerr << " failed to save the model of skeletons into file" << std::endl;
92+ }
93+
94+
4795// returns the number of processed input files.
48- int batch_reconstruct (std::vector<std::string>& point_cloud_files, const std::string& output_folder) {
96+ int batch_reconstruct (std::vector<std::string>& point_cloud_files, const std::string& output_folder, bool export_skeleton ) {
4997 int count (0 );
5098 for (std::size_t i=0 ; i<point_cloud_files.size (); ++i) {
5199 const std::string& xyz_file = point_cloud_files[i];
@@ -111,7 +159,12 @@ int batch_reconstruct(std::vector<std::string>& point_cloud_files, const std::st
111159 ++count;
112160 }
113161 else
114- std::cerr << " failed in saving the model of branches" << std::endl;
162+ std::cerr << " failed to save the model of branches" << std::endl;
163+
164+ if (export_skeleton) {
165+ const std::string& skeleton_file = output_folder + " /" + file_system::base_name (cloud->name ()) + " _skeleton.ply" ;
166+ save_skeleton (skeleton, cloud, skeleton_file);
167+ }
115168
116169 delete cloud;
117170 delete mesh;
@@ -123,15 +176,30 @@ int batch_reconstruct(std::vector<std::string>& point_cloud_files, const std::st
123176
124177
125178int main (int argc, char *argv[]) {
126- // argc = 3 ;
179+ // argc = 2 ;
127180// argv[1] = "/Users/lnan/Projects/adtree/data";
128181// argv[2] = "/Users/lnan/Projects/adtree/data-results";
129182
130183 if (argc == 1 ) {
131184 TreeViewer viewer;
132185 viewer.run ();
133186 return EXIT_SUCCESS;
134- } else if (argc == 3 ) {
187+ } else if (argc >= 3 ) {
188+ bool export_skeleton = false ;
189+ for (int i = 0 ; i < argc; ++i) {
190+ if (strcmp (argv[i], " -s" ) == 0 || strcmp (argv[i], " -skeleton" ) == 0 ) {
191+ export_skeleton = true ;
192+ break ;
193+ }
194+ }
195+
196+ if (export_skeleton) {
197+ std::cout << " You have requested to save the reconstructed tree skeleton(s) in PLY format into the output directory." << std::endl;
198+ std::cout << " The skeleton file(s) can be visualized using Easy3D: https://github.com/LiangliangNan/Easy3D" << std::endl;
199+ }
200+ else
201+ std::cout << " Tree skeleton(s) will not be saved (append '-s' or '-skeleton' in commandline to enable it)" << std::endl;
202+
135203 std::string first_arg (argv[1 ]);
136204 std::string second_arg (argv[2 ]);
137205 if (file_system::is_file (second_arg))
@@ -140,7 +208,7 @@ int main(int argc, char *argv[]) {
140208 std::string output_dir = second_arg;
141209 if (file_system::is_file (first_arg)) {
142210 std::vector<std::string> cloud_files = {first_arg};
143- return batch_reconstruct (cloud_files, output_dir) > 0 ;
211+ return batch_reconstruct (cloud_files, output_dir, export_skeleton ) > 0 ;
144212 } else if (file_system::is_directory (first_arg)) {
145213 std::vector<std::string> entries;
146214 file_system::get_directory_entries (first_arg, entries, false );
@@ -149,7 +217,7 @@ int main(int argc, char *argv[]) {
149217 if (file_name.size () > 3 && file_name.substr (file_name.size () - 3 ) == " xyz" )
150218 cloud_files.push_back (first_arg + " /" + file_name);
151219 }
152- return batch_reconstruct (cloud_files, output_dir) > 0 ;
220+ return batch_reconstruct (cloud_files, output_dir, export_skeleton ) > 0 ;
153221 } else
154222 std::cerr
155223 << " WARNING: unknown first argument (expecting either a point cloud file in *.xyz format or a\n "
@@ -158,13 +226,18 @@ int main(int argc, char *argv[]) {
158226 }
159227
160228 std::cerr << " Usage: AdTree can be run in three modes, which can be selected based on arguments:" << std::endl;
161- std::cerr << " - GUI mode." << std::endl;
162- std::cerr << " Command: ./AdTree" << std::endl;
163- std::cerr << " - Single processing mode (i.e., processing a single point cloud file)." << std::endl;
164- std::cerr << " Command: ./AdTree <xyz_file_path> <output_directory>" << std::endl;
165- std::cerr << " - Batch processing mode (i.e., all *.xyz files in the input directory will be treated as input \n " ;
166- std::cerr << " for reconstruction and the reconstructed models will be save in the output directory).\n " ;
167- std::cerr << " Command: ./AdTree <xyz_files_directory> <output_directory>" << std::endl;
229+ std::cerr << " 1) GUI mode." << std::endl;
230+ std::cerr << " Command: ./AdTree" << std::endl << std::endl;
231+ std::cerr << " 2) Commandline single processing mode (i.e., processing a single point cloud file)." << std::endl;
232+ std::cerr << " Command: ./AdTree <xyz_file_path> <output_directory> [-s|-skeleton]" << std::endl;
233+ std::cerr << " - <xyz_file_path>: a mandatory argument specifying the path to the input point cloud file" << std::endl;
234+ std::cerr << " - <output_directory>: a mandatory argument specifying where to save the results" << std::endl;
235+ std::cerr << " - [-s] or [-skeleton]: also export the skeletons (omit this argument it if you don't need skeletons)" << std::endl << std::endl;
236+ std::cerr << " 3) Commandline batch processing mode (i.e., all *.xyz files in an input directory will be processed)." << std::endl;
237+ std::cerr << " Command: ./AdTree <xyz_files_directory> <output_directory> [-s|-skeleton]" << std::endl;
238+ std::cerr << " - <xyz_files_directory>: a mandatory argument specifying the directory containing the input point cloud files" << std::endl;
239+ std::cerr << " - <output_directory>: a mandatory argument specifying where to save the results" << std::endl;
240+ std::cerr << " - [-s] or [-skeleton]: also export the skeletons (omit this argument it if you don't need skeletons)" << std::endl << std::endl;
168241
169242 return EXIT_FAILURE;
170243}
0 commit comments