11 #include "base/undistortion.h"
12 #include "controllers/texturing_controller.h"
13 #include "mvs/fusion.h"
14 #include "mvs/meshing.h"
15 #include "mvs/patch_match.h"
16 #include "ui/render_options.h"
17 #include "util/option_manager.h"
32 const static std::string kFusedFileName =
"fused.ply";
33 const static std::string kPoissonMeshedFileName =
"meshed-poisson.ply";
34 const static std::string kDelaunayMeshedFileName =
"meshed-delaunay.ply";
35 const static std::string kTexturedMeshFileName =
"textured-mesh.obj";
37 class StereoOptionsTab :
public colmap::OptionsWidget {
40 : OptionsWidget(parent) {
43 if (options->patch_match_stereo->max_image_size == -1) {
44 options->patch_match_stereo->max_image_size = 2000;
47 AddOptionInt(&options->patch_match_stereo->max_image_size,
48 "max_image_size", -1);
49 AddOptionText(&options->patch_match_stereo->gpu_index,
"gpu_index");
50 AddOptionDouble(&options->patch_match_stereo->depth_min,
"depth_min",
52 AddOptionDouble(&options->patch_match_stereo->depth_max,
"depth_max",
54 AddOptionInt(&options->patch_match_stereo->window_radius,
56 AddOptionInt(&options->patch_match_stereo->window_step,
"window_step");
57 AddOptionDouble(&options->patch_match_stereo->sigma_spatial,
59 AddOptionDouble(&options->patch_match_stereo->sigma_color,
61 AddOptionInt(&options->patch_match_stereo->num_samples,
"num_samples");
62 AddOptionDouble(&options->patch_match_stereo->ncc_sigma,
"ncc_sigma");
63 AddOptionDouble(&options->patch_match_stereo->min_triangulation_angle,
64 "min_triangulation_angle");
65 AddOptionDouble(&options->patch_match_stereo->incident_angle_sigma,
66 "incident_angle_sigma");
67 AddOptionInt(&options->patch_match_stereo->num_iterations,
69 AddOptionBool(&options->patch_match_stereo->geom_consistency,
72 &options->patch_match_stereo->geom_consistency_regularizer,
73 "geom_consistency_regularizer");
74 AddOptionDouble(&options->patch_match_stereo->geom_consistency_max_cost,
75 "geom_consistency_max_cost");
76 AddOptionBool(&options->patch_match_stereo->filter,
"filter");
77 AddOptionDouble(&options->patch_match_stereo->filter_min_ncc,
80 &options->patch_match_stereo->filter_min_triangulation_angle,
81 "filter_min_triangulation_angle");
82 AddOptionInt(&options->patch_match_stereo->filter_min_num_consistent,
83 "filter_min_num_consistent");
85 &options->patch_match_stereo->filter_geom_consistency_max_cost,
86 "filter_geom_consistency_max_cost");
87 AddOptionDouble(&options->patch_match_stereo->cache_size,
88 "cache_size [gigabytes]", 0,
90 AddOptionBool(&options->patch_match_stereo->write_consistency_graph,
91 "write_consistency_graph");
95 class FusionOptionsTab :
public colmap::OptionsWidget {
98 : OptionsWidget(parent) {
99 AddOptionInt(&options->stereo_fusion->max_image_size,
"max_image_size",
101 AddOptionInt(&options->stereo_fusion->min_num_pixels,
"min_num_pixels",
103 AddOptionInt(&options->stereo_fusion->max_num_pixels,
"max_num_pixels",
105 AddOptionInt(&options->stereo_fusion->max_traversal_depth,
106 "max_traversal_depth", 1);
107 AddOptionDouble(&options->stereo_fusion->max_reproj_error,
108 "max_reproj_error", 0);
109 AddOptionDouble(&options->stereo_fusion->max_depth_error,
110 "max_depth_error", 0, 1, 0.0001, 4);
111 AddOptionDouble(&options->stereo_fusion->max_normal_error,
112 "max_normal_error", 0, 180);
113 AddOptionInt(&options->stereo_fusion->check_num_images,
114 "check_num_images", 1);
115 AddOptionDouble(&options->stereo_fusion->cache_size,
116 "cache_size [gigabytes]", 0,
121 class MeshingOptionsTab :
public colmap::OptionsWidget {
124 : OptionsWidget(parent) {
125 AddSection(
"Poisson Meshing");
126 AddOptionDouble(&options->poisson_meshing->point_weight,
"point_weight",
128 AddOptionInt(&options->poisson_meshing->depth,
"depth", 1);
129 AddOptionDouble(&options->poisson_meshing->color,
"color", 0);
130 AddOptionDouble(&options->poisson_meshing->trim,
"trim", 0);
131 AddOptionInt(&options->poisson_meshing->num_threads,
"num_threads", -1);
133 AddSection(
"Delaunay Meshing");
134 AddOptionDouble(&options->delaunay_meshing->max_proj_dist,
136 AddOptionDouble(&options->delaunay_meshing->max_depth_dist,
137 "max_depth_dist", 0);
138 AddOptionDouble(&options->delaunay_meshing->distance_sigma_factor,
139 "distance_sigma_factor", 0);
140 AddOptionDouble(&options->delaunay_meshing->quality_regularization,
141 "quality_regularization", 0);
142 AddOptionDouble(&options->delaunay_meshing->max_side_length_factor,
143 "max_side_length_factor", 0);
144 AddOptionDouble(&options->delaunay_meshing->max_side_length_percentile,
145 "max_side_length_percentile", 0);
146 AddOptionInt(&options->delaunay_meshing->num_threads,
"num_threads",
151 class TexturingOptionsTab :
public colmap::OptionsWidget {
154 : OptionsWidget(parent),
156 mesh_source_combo_(nullptr) {
157 AddOptionBool(&options->texturing->verbose,
"verbose");
160 mesh_source_combo_ =
new QComboBox(
this);
161 mesh_source_combo_->addItem(
"Auto (Delaunay preferred)",
"auto");
162 mesh_source_combo_->addItem(
"Delaunay",
"delaunay");
163 mesh_source_combo_->addItem(
"Poisson",
"poisson");
166 connect(mesh_source_combo_,
167 QOverload<int>::of(&QComboBox::currentIndexChanged),
170 mesh_source_combo_->itemData(index).
toString();
171 options_->texturing->mesh_source = data.toStdString();
174 AddWidgetRow(
"mesh_source", mesh_source_combo_);
176 AddOptionFilePath(&options->texturing->meshed_file_path,
178 AddOptionDirPath(&options->texturing->textured_file_path,
179 "textured_file_path");
181 AddSection(
"Advanced Options");
182 AddOptionBool(&options->texturing->use_depth_normal_maps,
183 "use_depth_normal_maps");
186 QComboBox* depth_type_combo =
new QComboBox(
this);
187 depth_type_combo->addItem(
"Geometric",
"geometric");
188 depth_type_combo->addItem(
"Photometric",
"photometric");
189 connect(depth_type_combo,
190 QOverload<int>::of(&QComboBox::currentIndexChanged),
191 [
this, depth_type_combo, options](
int index) {
192 QString data = depth_type_combo->itemData(index).
toString();
193 options->texturing->depth_map_type = data.toStdString();
196 if (options->texturing->depth_map_type ==
"geometric") {
197 depth_type_combo->setCurrentIndex(0);
199 depth_type_combo->setCurrentIndex(1);
201 AddWidgetRow(
"depth_map_type", depth_type_combo);
203 AddOptionDouble(&options->texturing->max_depth_error,
"max_depth_error",
205 AddOptionDouble(&options->texturing->min_normal_consistency,
206 "min_normal_consistency", -1, 1, 0.01, 2);
207 AddOptionDouble(&options->texturing->max_viewing_angle_deg,
208 "max_viewing_angle_deg", 0, 180, 1, 1);
209 AddOptionBool(&options->texturing->use_gradient_magnitude,
210 "use_gradient_magnitude");
214 void showEvent(QShowEvent*
event)
override {
215 OptionsWidget::showEvent(
event);
217 if (mesh_source_combo_) {
218 std::string current_source = options_->texturing->mesh_source;
219 if (current_source ==
"poisson") {
220 mesh_source_combo_->setCurrentIndex(2);
221 }
else if (current_source ==
"delaunay") {
222 mesh_source_combo_->setCurrentIndex(1);
224 mesh_source_combo_->setCurrentIndex(0);
231 QComboBox* mesh_source_combo_;
235 std::vector<std::pair<std::string, std::string>> ReadPatchMatchConfig(
236 const std::string& config_path) {
237 std::ifstream file(config_path);
238 CHECK(file.is_open()) << config_path;
241 std::string ref_image_name;
242 std::vector<std::pair<std::string, std::string>> images;
243 while (std::getline(file, line)) {
244 colmap::StringTrim(&line);
246 if (line.empty() || line[0] ==
'#') {
250 if (ref_image_name.empty()) {
251 ref_image_name = line;
253 images.emplace_back(ref_image_name, line);
254 ref_image_name.clear();
267 setWindowFlags(Qt::Dialog);
268 setWindowModality(Qt::ApplicationModal);
269 setWindowTitle(
"Dense reconstruction options");
271 QGridLayout* grid =
new QGridLayout(
this);
273 QTabWidget* tab_widget =
new QTabWidget(
this);
274 tab_widget->setElideMode(Qt::TextElideMode::ElideRight);
275 tab_widget->addTab(
new StereoOptionsTab(
this, options),
"Stereo");
276 tab_widget->addTab(
new FusionOptionsTab(
this, options),
"Fusion");
277 tab_widget->addTab(
new MeshingOptionsTab(
this, options),
"Meshing");
278 tab_widget->addTab(
new TexturingOptionsTab(
this, options),
"Texturing");
280 grid->addWidget(tab_widget, 0, 0);
285 : QWidget(main_window),
286 main_window_(main_window),
288 reconstruction_(nullptr),
291 photometric_done_(false),
292 geometric_done_(false) {
293 setWindowFlags(Qt::Dialog);
294 setWindowModality(Qt::ApplicationModal);
295 setWindowTitle(
"Dense reconstruction");
296 resize(main_window_->size().width() - 20,
297 main_window_->size().height() - 20);
299 QGridLayout* grid =
new QGridLayout(
this);
301 undistortion_button_ =
new QPushButton(tr(
"Undistortion"),
this);
302 connect(undistortion_button_, &QPushButton::released,
this,
303 &DenseReconstructionWidget::Undistort);
304 grid->addWidget(undistortion_button_, 0, 0, Qt::AlignLeft);
306 stereo_button_ =
new QPushButton(tr(
"Stereo"),
this);
307 connect(stereo_button_, &QPushButton::released,
this,
308 &DenseReconstructionWidget::Stereo);
309 grid->addWidget(stereo_button_, 0, 1, Qt::AlignLeft);
311 fusion_button_ =
new QPushButton(tr(
"Fusion"),
this);
312 connect(fusion_button_, &QPushButton::released,
this,
313 &DenseReconstructionWidget::Fusion);
314 grid->addWidget(fusion_button_, 0, 2, Qt::AlignLeft);
316 poisson_meshing_button_ =
new QPushButton(tr(
"Poisson"),
this);
317 connect(poisson_meshing_button_, &QPushButton::released,
this,
318 &DenseReconstructionWidget::PoissonMeshing);
319 grid->addWidget(poisson_meshing_button_, 0, 3, Qt::AlignLeft);
321 delaunay_meshing_button_ =
new QPushButton(tr(
"Delaunay"),
this);
322 connect(delaunay_meshing_button_, &QPushButton::released,
this,
323 &DenseReconstructionWidget::DelaunayMeshing);
324 grid->addWidget(delaunay_meshing_button_, 0, 4, Qt::AlignLeft);
326 texturing_button_ =
new QPushButton(tr(
"Texturing"),
this);
327 connect(texturing_button_, &QPushButton::released,
this,
328 &DenseReconstructionWidget::Texturing);
329 grid->addWidget(texturing_button_, 0, 5, Qt::AlignLeft);
331 QPushButton* options_button =
new QPushButton(tr(
"Options"),
this);
332 connect(options_button, &QPushButton::released, options_widget_,
333 &OptionsWidget::show);
334 grid->addWidget(options_button, 0, 6, Qt::AlignLeft);
336 QLabel* workspace_path_label =
new QLabel(
"Workspace",
this);
337 grid->addWidget(workspace_path_label, 0, 7, Qt::AlignRight);
339 workspace_path_text_ =
new QLineEdit(
this);
340 grid->addWidget(workspace_path_text_, 0, 8, Qt::AlignRight);
341 connect(workspace_path_text_, &QLineEdit::textChanged,
this,
342 &DenseReconstructionWidget::RefreshWorkspace, Qt::QueuedConnection);
344 QPushButton* refresh_path_button =
new QPushButton(tr(
"Refresh"),
this);
345 connect(refresh_path_button, &QPushButton::released,
this,
346 &DenseReconstructionWidget::RefreshWorkspace, Qt::QueuedConnection);
347 grid->addWidget(refresh_path_button, 0, 9, Qt::AlignRight);
349 QPushButton* workspace_path_button =
new QPushButton(tr(
"Select"),
this);
350 connect(workspace_path_button, &QPushButton::released,
this,
351 &DenseReconstructionWidget::SelectWorkspacePath,
352 Qt::QueuedConnection);
353 grid->addWidget(workspace_path_button, 0, 10, Qt::AlignRight);
355 QStringList table_header;
356 table_header <<
"image_name"
362 table_widget_ =
new QTableWidget(
this);
363 table_widget_->setColumnCount(table_header.size());
364 table_widget_->setHorizontalHeaderLabels(table_header);
366 table_widget_->setShowGrid(
true);
367 table_widget_->setSelectionBehavior(QAbstractItemView::SelectRows);
368 table_widget_->setSelectionMode(QAbstractItemView::SingleSelection);
369 table_widget_->setEditTriggers(QAbstractItemView::NoEditTriggers);
370 table_widget_->verticalHeader()->setDefaultSectionSize(25);
372 grid->addWidget(table_widget_, 1, 0, 1, 11);
374 grid->setColumnStretch(4, 1);
377 image_viewer_widget_->setWindowModality(Qt::ApplicationModal);
379 refresh_workspace_action_ =
new QAction(
this);
380 connect(refresh_workspace_action_, &QAction::triggered,
this,
381 &DenseReconstructionWidget::RefreshWorkspace);
383 write_fused_points_action_ =
new QAction(
this);
384 connect(write_fused_points_action_, &QAction::triggered,
this,
385 &DenseReconstructionWidget::WriteFusedPoints);
387 show_meshing_info_action_ =
new QAction(
this);
388 connect(show_meshing_info_action_, &QAction::triggered,
this,
389 &DenseReconstructionWidget::ShowMeshingInfo);
394 void DenseReconstructionWidget::showEvent(QShowEvent*
event) {
398 if (workspace_path_text_->text().isEmpty()) {
399 std::string default_workspace;
403 if (options_->project_path && !options_->project_path->empty()) {
404 default_workspace = GetParentDir(*options_->project_path);
405 if (ExistsDir(default_workspace)) {
406 workspace_path_text_->setText(
407 QString::fromStdString(default_workspace));
409 QString(
"Auto-loaded workspace from project_path: %1")
410 .arg(default_workspace.c_str()));
417 if (options_->database_path && !options_->database_path->empty()) {
418 default_workspace = GetParentDir(*options_->database_path);
419 if (ExistsDir(default_workspace)) {
420 workspace_path_text_->setText(
421 QString::fromStdString(default_workspace));
423 QString(
"Auto-loaded workspace from database_path: %1")
424 .arg(default_workspace.c_str()));
431 if (options_->image_path && !options_->image_path->empty()) {
433 default_workspace = *options_->image_path;
434 if (ExistsDir(default_workspace)) {
436 default_workspace = GetParentDir(default_workspace);
437 if (ExistsDir(default_workspace)) {
438 workspace_path_text_->setText(
439 QString::fromStdString(default_workspace));
441 QString(
"Auto-loaded workspace from image_path: %1")
442 .arg(default_workspace.c_str()));
449 "Could not auto-load workspace path. Please select manually."));
456 reconstruction_ = reconstruction;
461 void DenseReconstructionWidget::Undistort() {
462 const std::string workspace_path = GetWorkspacePath();
463 if (workspace_path.empty()) {
467 if (reconstruction_ ==
nullptr || reconstruction_->NumRegImages() < 2) {
468 QMessageBox::critical(
this,
"",
469 tr(
"No reconstruction selected in main window"));
474 COLMAPUndistorter* undistorter =
475 new COLMAPUndistorter(UndistortCameraOptions(), reconstruction_,
476 *options_->image_path, workspace_path);
477 undistorter->AddCallback(Thread::FINISHED_CALLBACK, [
this]() {
478 refresh_workspace_action_->trigger();
480 thread_control_widget_->
StartThread(
"Undistorting...",
true, undistorter);
483 void DenseReconstructionWidget::Stereo() {
484 const std::string workspace_path = GetWorkspacePath();
485 if (workspace_path.empty()) {
490 mvs::PatchMatchController* processor =
new mvs::PatchMatchController(
491 *options_->patch_match_stereo, workspace_path,
"COLMAP",
"");
492 processor->AddCallback(Thread::FINISHED_CALLBACK,
493 [
this]() { refresh_workspace_action_->trigger(); });
494 thread_control_widget_->
StartThread(
"Stereo...",
true, processor);
496 QMessageBox::critical(
this,
"",
497 tr(
"Dense stereo reconstruction requires CUDA, which "
498 "is not available on your system."));
502 void DenseReconstructionWidget::Fusion() {
503 const std::string workspace_path = GetWorkspacePath();
504 if (workspace_path.empty()) {
508 std::string input_type;
509 if (geometric_done_) {
510 input_type =
"geometric";
511 }
else if (photometric_done_) {
512 input_type =
"photometric";
514 QMessageBox::critical(
515 this,
"", tr(
"All images must be processed prior to fusion"));
518 mvs::StereoFusion* fuser =
new mvs::StereoFusion(
519 *options_->stereo_fusion, workspace_path,
"COLMAP",
"", input_type);
520 fuser->AddCallback(Thread::FINISHED_CALLBACK, [
this, fuser]() {
521 fused_points_ = fuser->GetFusedPoints();
522 fused_points_visibility_ = fuser->GetFusedPointsVisibility();
523 write_fused_points_action_->trigger();
525 thread_control_widget_->
StartThread(
"Fusion...",
true, fuser);
528 void DenseReconstructionWidget::PoissonMeshing() {
529 const std::string workspace_path = GetWorkspacePath();
530 if (workspace_path.empty()) {
534 if (ExistsFile(
JoinPaths(workspace_path, kFusedFileName))) {
536 "Poisson Meshing...", [
this, workspace_path]() {
538 *options_->poisson_meshing,
539 JoinPaths(workspace_path, kFusedFileName),
540 JoinPaths(workspace_path, kPoissonMeshedFileName));
542 JoinPaths(workspace_path, kPoissonMeshedFileName);
543 refresh_workspace_action_->trigger();
544 show_meshing_info_action_->trigger();
549 void DenseReconstructionWidget::DelaunayMeshing() {
551 const std::string workspace_path = GetWorkspacePath();
552 if (workspace_path.empty()) {
556 if (ExistsFile(
JoinPaths(workspace_path, kFusedFileName))) {
558 "Delaunay Meshing...", [
this, workspace_path]() {
559 mvs::DenseDelaunayMeshing(
560 *options_->delaunay_meshing, workspace_path,
561 JoinPaths(workspace_path, kDelaunayMeshedFileName));
563 JoinPaths(workspace_path, kDelaunayMeshedFileName);
564 refresh_workspace_action_->trigger();
565 show_meshing_info_action_->trigger();
569 QMessageBox::critical(
this,
"",
570 tr(
"Delaunay meshing requires CGAL, which "
571 "is not available on your system."));
575 void DenseReconstructionWidget::Texturing() {
576 const std::string workspace_path = GetWorkspacePath();
577 if (workspace_path.empty()) {
581 if (reconstruction_ ==
nullptr || reconstruction_->NumRegImages() < 2) {
582 QMessageBox::critical(
this,
"",
583 tr(
"No reconstruction selected in main window"));
588 if (!ExistsFile(options_->texturing->meshed_file_path)) {
589 const std::string poisson_path =
590 JoinPaths(workspace_path, kPoissonMeshedFileName);
591 const std::string delaunay_path =
592 JoinPaths(workspace_path, kDelaunayMeshedFileName);
593 const bool poisson_exists = ExistsFile(poisson_path);
594 const bool delaunay_exists = ExistsFile(delaunay_path);
596 if (options_->texturing->mesh_source ==
"delaunay") {
597 if (delaunay_exists) {
598 options_->texturing->meshed_file_path = delaunay_path;
600 QMessageBox::critical(
this,
"",
601 tr(
"Delaunay mesh file not found. Please "
602 "run Delaunay meshing first."));
605 }
else if (options_->texturing->mesh_source ==
"poisson") {
606 if (poisson_exists) {
607 options_->texturing->meshed_file_path = poisson_path;
609 QMessageBox::critical(
this,
"",
610 tr(
"Poisson mesh file not found. Please "
611 "run Poisson meshing first."));
615 if (delaunay_exists) {
616 options_->texturing->meshed_file_path = delaunay_path;
617 }
else if (poisson_exists) {
618 options_->texturing->meshed_file_path = poisson_path;
620 QMessageBox::critical(
this,
"",
621 tr(
"No mesh file found. Please run "
622 "Poisson or Delaunay meshing first."));
629 if (options_->texturing->textured_file_path.empty()) {
630 options_->texturing->textured_file_path =
631 JoinPaths(workspace_path, kTexturedMeshFileName);
634 options_->texturing->textured_file_path);
635 CreateDirIfNotExists(parent_path);
636 std::string
name, ext;
640 if (ext !=
".obj" && ext !=
".OBJ") {
641 options_->texturing->textured_file_path =
642 JoinPaths(parent_path, kTexturedMeshFileName);
646 colmap::TexturingReconstruction* texturingTool =
647 new colmap::TexturingReconstruction(
648 *options_->texturing, *reconstruction_,
649 *options_->image_path, workspace_path);
650 texturingTool->AddCallback(Thread::FINISHED_CALLBACK, [
this]() {
651 out_mesh_path_ = options_->texturing->textured_file_path;
652 show_meshing_info_action_->trigger();
654 thread_control_widget_->
StartThread(
"Texturing...",
true, texturingTool);
657 void DenseReconstructionWidget::SelectWorkspacePath() {
658 std::string workspace_path;
659 if (workspace_path_text_->text().isEmpty()) {
660 workspace_path = GetParentDir(*options_->project_path);
662 workspace_path = workspace_path_text_->text().toUtf8().constData();
665 workspace_path_text_->setText(QFileDialog::getExistingDirectory(
666 this, tr(
"Select workspace path..."),
667 QString::fromStdString(workspace_path), QFileDialog::ShowDirsOnly));
672 std::string DenseReconstructionWidget::GetWorkspacePath() {
673 const std::string workspace_path =
674 workspace_path_text_->text().toUtf8().constData();
675 if (ExistsDir(workspace_path)) {
676 return workspace_path;
678 QMessageBox::critical(
this,
"", tr(
"Invalid workspace path"));
683 void DenseReconstructionWidget::RefreshWorkspace() {
684 table_widget_->clearContents();
685 table_widget_->setRowCount(0);
687 const std::string workspace_path =
688 workspace_path_text_->text().toUtf8().constData();
689 if (ExistsDir(workspace_path)) {
690 undistortion_button_->setEnabled(
true);
692 undistortion_button_->setEnabled(
false);
693 stereo_button_->setEnabled(
false);
694 fusion_button_->setEnabled(
false);
695 poisson_meshing_button_->setEnabled(
false);
696 delaunay_meshing_button_->setEnabled(
false);
697 texturing_button_->setEnabled(
false);
701 images_path_ =
JoinPaths(workspace_path,
"images");
702 depth_maps_path_ =
JoinPaths(workspace_path,
"stereo/depth_maps");
703 normal_maps_path_ =
JoinPaths(workspace_path,
"stereo/normal_maps");
704 const std::string config_path =
705 JoinPaths(workspace_path,
"stereo/patch-match.cfg");
707 if (ExistsDir(images_path_) && ExistsDir(depth_maps_path_) &&
708 ExistsDir(normal_maps_path_) &&
709 ExistsDir(
JoinPaths(workspace_path,
"sparse")) &&
710 ExistsDir(
JoinPaths(workspace_path,
"stereo/consistency_graphs")) &&
711 ExistsFile(config_path)) {
712 stereo_button_->setEnabled(
true);
714 stereo_button_->setEnabled(
false);
715 fusion_button_->setEnabled(
false);
716 poisson_meshing_button_->setEnabled(
false);
717 delaunay_meshing_button_->setEnabled(
false);
718 texturing_button_->setEnabled(
false);
722 const auto images = ReadPatchMatchConfig(config_path);
723 table_widget_->setRowCount(images.size());
725 for (
size_t i = 0; i < images.size(); ++i) {
726 const std::string image_name = images[i].first;
727 const std::string src_images = images[i].second;
728 const std::string image_path =
JoinPaths(images_path_, image_name);
730 QTableWidgetItem* image_name_item =
731 new QTableWidgetItem(QString::fromStdString(image_name));
732 table_widget_->setItem(i, 0, image_name_item);
734 QPushButton* image_button =
new QPushButton(
"Image");
735 connect(image_button, &QPushButton::released,
736 [
this, image_name, image_path]() {
737 image_viewer_widget_->setWindowTitle(
738 QString(
"Image for %1").arg(image_name.c_str()));
741 table_widget_->setCellWidget(i, 1, image_button);
743 table_widget_->setCellWidget(
744 i, 2, GenerateTableButtonWidget(image_name,
"photometric"));
745 table_widget_->setCellWidget(
746 i, 3, GenerateTableButtonWidget(image_name,
"geometric"));
748 QTableWidgetItem* src_images_item =
749 new QTableWidgetItem(QString::fromStdString(src_images));
750 table_widget_->setItem(i, 4, src_images_item);
753 table_widget_->resizeColumnsToContents();
755 fusion_button_->setEnabled(photometric_done_ || geometric_done_);
756 poisson_meshing_button_->setEnabled(
757 ExistsFile(
JoinPaths(workspace_path, kFusedFileName)));
758 delaunay_meshing_button_->setEnabled(
759 ExistsFile(
JoinPaths(workspace_path, kFusedFileName)));
761 texturing_button_->setEnabled(
762 ExistsFile(
JoinPaths(workspace_path, kPoissonMeshedFileName)) ||
763 ExistsFile(
JoinPaths(workspace_path, kDelaunayMeshedFileName)));
766 void DenseReconstructionWidget::WriteFusedPoints() {
767 const int reply = QMessageBox::question(
769 tr(
"Do you want to add the dense fused point cloud to DBRoot? "
770 "Otherwise, to visualize "
771 "the reconstructed dense point cloud later, navigate to the "
772 "<i>dense</i> sub-folder in your workspace with <i>File > "
774 "model from...</i>."),
775 QMessageBox::Yes | QMessageBox::No);
776 if (reply == QMessageBox::Yes) {
778 unsigned nPoints =
static_cast<unsigned>(fused_points_.size());
782 for (
const auto&
point : fused_points_) {
787 if (main_window_->app_) {
788 main_window_->app_->
addToDB(cloud);
792 "[DenseReconstructionWidget::WriteFusedPoints] Not "
798 const std::string workspace_path =
799 workspace_path_text_->text().toUtf8().constData();
800 if (workspace_path.empty()) {
802 fused_points_visibility_ = {};
807 "Exporting...", [
this, workspace_path]() {
808 const std::string output_path =
809 JoinPaths(workspace_path, kFusedFileName);
810 WriteBinaryPlyPoints(output_path, fused_points_);
811 mvs::WritePointsVisibility(output_path +
".vis",
812 fused_points_visibility_);
814 fused_points_visibility_ = {};
815 poisson_meshing_button_->setEnabled(
true);
816 delaunay_meshing_button_->setEnabled(
true);
820 void DenseReconstructionWidget::ShowMeshingInfo() {
821 if (!ExistsFile(out_mesh_path_)) {
825 const int reply = QMessageBox::question(
827 tr(
"Do you want to add the meshed model to DBRoot, Otherwise, to "
829 "the reconstructed dense point cloud later, navigate to the "
831 "The model is located in the workspace folder."),
832 QMessageBox::Yes | QMessageBox::No);
833 if (reply == QMessageBox::Yes) {
834 if (main_window_->app_) {
835 main_window_->app_->
addToDBAuto(QStringList(out_mesh_path_.c_str()),
841 QWidget* DenseReconstructionWidget::GenerateTableButtonWidget(
842 const std::string& image_name,
const std::string&
type) {
843 CHECK(
type ==
"photometric" ||
type ==
"geometric");
844 const bool photometric =
type ==
"photometric";
847 photometric_done_ =
true;
849 geometric_done_ =
true;
852 const std::string depth_map_path =
JoinPaths(
855 const std::string normal_map_path =
JoinPaths(
859 QWidget* button_widget =
new QWidget();
860 QGridLayout* button_layout =
new QGridLayout(button_widget);
861 button_layout->setContentsMargins(0, 0, 0, 0);
863 QPushButton* depth_map_button =
new QPushButton(
"Depth map", button_widget);
864 if (ExistsFile(depth_map_path)) {
865 connect(depth_map_button, &QPushButton::released,
866 [
this, image_name, depth_map_path]() {
867 mvs::DepthMap depth_map;
868 depth_map.Read(depth_map_path);
869 image_viewer_widget_->setWindowTitle(
870 QString(
"Depth map for %1")
871 .arg(image_name.c_str()));
872 image_viewer_widget_->
ShowBitmap(depth_map.ToBitmap(2, 98));
875 depth_map_button->setEnabled(
false);
877 photometric_done_ =
false;
879 geometric_done_ =
false;
882 button_layout->addWidget(depth_map_button, 0, 1, Qt::AlignLeft);
884 QPushButton* normal_map_button =
885 new QPushButton(
"Normal map", button_widget);
886 if (ExistsFile(normal_map_path)) {
887 connect(normal_map_button, &QPushButton::released,
888 [
this, image_name, normal_map_path]() {
889 mvs::NormalMap normal_map;
890 normal_map.Read(normal_map_path);
891 image_viewer_widget_->setWindowTitle(
892 QString(
"Normal map for %1")
893 .arg(image_name.c_str()));
894 image_viewer_widget_->
ShowBitmap(normal_map.ToBitmap());
897 normal_map_button->setEnabled(
false);
899 photometric_done_ =
false;
901 geometric_done_ =
false;
904 button_layout->addWidget(normal_map_button, 0, 2, Qt::AlignLeft);
906 return button_widget;
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
static bool Print(const char *format,...)
Prints out a formatted message in console.
void addToDB(const QStringList &filenames, QString fileFilter=QString(), bool displayDialog=true)
void addToDBAuto(const QStringList &filenames, bool displayDialog=true)
virtual void showColors(bool state)
Sets colors visibility.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
void addRGBColor(const ecvColor::Rgb &C)
Pushes an RGB color on stack.
bool reserveThePointsTable(unsigned _numberOfPoints)
Reserves memory to store the points coordinates.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
std::string GetFileParentDirectory(const std::string &filename)
void SplitFileExtension(const std::string &path, std::string *root, std::string *ext)
std::string JoinPaths(T const &...paths)
std::string StringPrintf(const char *format,...)
Generic file read and write utility for python interface.
colmap::OptionManager OptionManager
std::string toString(T x)