ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
PointViewerWidget.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "PointViewerWidget.h"
9 
10 #include "ModelViewerWidget.h"
11 #include "ui/qt_utils.h"
12 #include "util/bitmap.h"
13 #include "util/misc.h"
14 #include "util/option_manager.h"
15 
16 namespace cloudViewer {
17 
18 using namespace colmap;
20  ModelViewerWidget* model_viewer_widget,
21  OptionManager* options)
22  : QWidget(parent),
23  model_viewer_widget_(model_viewer_widget),
24  options_(options),
25  point3D_id_(kInvalidPoint3DId),
26  zoom_(250.0 / 1024.0) {
27  setWindowFlags(Qt::Window);
28  resize(parent->size().width() - 20, parent->size().height() - 20);
29 
30  QFont font;
31  font.setPointSize(10);
32  setFont(font);
33 
34  QGridLayout* grid = new QGridLayout(this);
35  grid->setContentsMargins(5, 5, 5, 5);
36 
37  info_table_ = new QTableWidget(this);
38  info_table_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
39  info_table_->setEditTriggers(QAbstractItemView::NoEditTriggers);
40  info_table_->setSelectionMode(QAbstractItemView::SingleSelection);
41  info_table_->setShowGrid(true);
42  info_table_->horizontalHeader()->setStretchLastSection(true);
43  info_table_->horizontalHeader()->setVisible(false);
44  info_table_->verticalHeader()->setVisible(false);
45  info_table_->verticalHeader()->setDefaultSectionSize(18);
46 
47  info_table_->setColumnCount(2);
48  info_table_->setRowCount(3);
49 
50  info_table_->setItem(0, 0, new QTableWidgetItem("position"));
51  xyz_item_ = new QTableWidgetItem();
52  info_table_->setItem(0, 1, xyz_item_);
53 
54  info_table_->setItem(1, 0, new QTableWidgetItem("color"));
55  rgb_item_ = new QTableWidgetItem();
56  info_table_->setItem(1, 1, rgb_item_);
57 
58  info_table_->setItem(2, 0, new QTableWidgetItem("error"));
59  error_item_ = new QTableWidgetItem();
60  info_table_->setItem(2, 1, error_item_);
61 
62  grid->addWidget(info_table_, 0, 0);
63 
64  location_table_ = new QTableWidget(this);
65  location_table_->setColumnCount(4);
66  QStringList table_header;
67  table_header << "image_id"
68  << "reproj_error"
69  << "track_location"
70  << "image_name";
71  location_table_->setHorizontalHeaderLabels(table_header);
72  location_table_->resizeColumnsToContents();
73  location_table_->setShowGrid(true);
74  location_table_->horizontalHeader()->setStretchLastSection(true);
75  location_table_->verticalHeader()->setVisible(true);
76  location_table_->setSelectionMode(QAbstractItemView::NoSelection);
77  location_table_->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
78  location_table_->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
79  location_table_->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
80 
81  grid->addWidget(location_table_, 1, 0);
82 
83  QHBoxLayout* button_layout = new QHBoxLayout();
84 
85  zoom_in_button_ = new QPushButton(tr("+"), this);
86  zoom_in_button_->setFont(font);
87  zoom_in_button_->setFixedWidth(50);
88  button_layout->addWidget(zoom_in_button_);
89  connect(zoom_in_button_, &QPushButton::released, this,
90  &PointViewerWidget::ZoomIn);
91 
92  zoom_out_button_ = new QPushButton(tr("-"), this);
93  zoom_out_button_->setFont(font);
94  zoom_out_button_->setFixedWidth(50);
95  button_layout->addWidget(zoom_out_button_);
96  connect(zoom_out_button_, &QPushButton::released, this,
97  &PointViewerWidget::ZoomOut);
98 
99  delete_button_ = new QPushButton(tr("Delete"), this);
100  button_layout->addWidget(delete_button_);
101  connect(delete_button_, &QPushButton::released, this,
102  &PointViewerWidget::Delete);
103 
104  grid->addLayout(button_layout, 2, 0, Qt::AlignRight);
105 }
106 
107 void PointViewerWidget::Show(const point3D_t point3D_id) {
108  location_pixmaps_.clear();
109  image_ids_.clear();
110  reproj_errors_.clear();
111  image_names_.clear();
112 
113  if (model_viewer_widget_->points3D.count(point3D_id) == 0) {
114  point3D_id_ = kInvalidPoint3DId;
115  ClearLocations();
116  return;
117  }
118 
119  show();
120  raise();
121 
122  point3D_id_ = point3D_id;
123 
124  // Show some general information about the point.
125 
126  setWindowTitle(
127  QString::fromStdString("Point " + std::to_string(point3D_id)));
128 
129  const auto& point3D = model_viewer_widget_->points3D[point3D_id];
130 
131  xyz_item_->setText(QString::number(point3D.X()) + ", " +
132  QString::number(point3D.Y()) + ", " +
133  QString::number(point3D.Z()));
134  rgb_item_->setText(QString::number(point3D.Color(0)) + ", " +
135  QString::number(point3D.Color(1)) + ", " +
136  QString::number(point3D.Color(2)));
137  error_item_->setText(QString::number(point3D.Error()));
138 
139  ResizeInfoTable();
140 
141  // Sort the track elements by the image names.
142 
143  std::vector<std::pair<TrackElement, std::string>>
144  track_idx_image_name_pairs;
145  track_idx_image_name_pairs.reserve(point3D.Track().Length());
146  for (const auto& track_el : point3D.Track().Elements()) {
147  const Image& image = model_viewer_widget_->images[track_el.image_id];
148  track_idx_image_name_pairs.emplace_back(track_el, image.Name());
149  }
150 
151  std::sort(track_idx_image_name_pairs.begin(),
152  track_idx_image_name_pairs.end(),
153  [](const std::pair<TrackElement, std::string>& track_el1,
154  const std::pair<TrackElement, std::string>& track_el2) {
155  return track_el1.second < track_el2.second;
156  });
157 
158  // Paint features for each track element.
159 
160  for (const auto& track_el : track_idx_image_name_pairs) {
161  const Image& image =
162  model_viewer_widget_->images[track_el.first.image_id];
163  const Camera& camera = model_viewer_widget_->cameras[image.CameraId()];
164  const Point2D& point2D = image.Point2D(track_el.first.point2D_idx);
165  const Eigen::Vector2d proj_point2D = ProjectPointToImage(
166  point3D.XYZ(), image.ProjectionMatrix(), camera);
167  const double reproj_error = (point2D.XY() - proj_point2D).norm();
168 
169  colmap::Bitmap bitmap;
170  const std::string path = JoinPaths(*options_->image_path, image.Name());
171  if (!bitmap.Read(path, true)) {
172  std::cerr << "ERROR: Cannot read image at path " << path
173  << std::endl;
174  continue;
175  }
176 
177  QPixmap pixmap = QPixmap::fromImage(BitmapToQImageRGB(bitmap));
178 
179  // Paint feature in current image.
180  QPainter painter(&pixmap);
181  painter.setRenderHint(QPainter::Antialiasing);
182 
183  QPen pen;
184  pen.setWidth(3);
185  pen.setColor(Qt::green);
186  painter.setPen(pen);
187 
188  const int kCrossSize = 15;
189  const int x = static_cast<int>(std::round(point2D.X()));
190  const int y = static_cast<int>(std::round(point2D.Y()));
191  painter.drawLine(x - kCrossSize, y - kCrossSize, x + kCrossSize,
192  y + kCrossSize);
193  painter.drawLine(x - kCrossSize, y + kCrossSize, x + kCrossSize,
194  y - kCrossSize);
195 
196  pen.setColor(Qt::red);
197  painter.setPen(pen);
198 
199  const int proj_x = static_cast<int>(std::round(proj_point2D.x()));
200  const int proj_y = static_cast<int>(std::round(proj_point2D.y()));
201  painter.drawEllipse(proj_x - 5, proj_y - 5, 10, 10);
202  painter.drawEllipse(proj_x - 15, proj_y - 15, 30, 30);
203  painter.drawEllipse(proj_x - 45, proj_y - 45, 90, 90);
204 
205  location_pixmaps_.push_back(pixmap);
206  image_ids_.push_back(track_el.first.image_id);
207  reproj_errors_.push_back(reproj_error);
208  image_names_.push_back(image.Name());
209  }
210 
211  UpdateImages();
212 }
213 
214 void PointViewerWidget::closeEvent(QCloseEvent* event) {
215  // Release the images, since zoomed in images can use a lot of memory.
216  location_pixmaps_.clear();
217  image_ids_.clear();
218  reproj_errors_.clear();
219  image_names_.clear();
220  ClearLocations();
221 }
222 
223 void PointViewerWidget::ResizeInfoTable() {
224  // Set fixed table dimensions.
225  info_table_->resizeColumnsToContents();
226  int height = info_table_->horizontalHeader()->height() +
227  2 * info_table_->frameWidth();
228  for (int i = 0; i < info_table_->rowCount(); i++) {
229  height += info_table_->rowHeight(i);
230  }
231  info_table_->setFixedHeight(height);
232 }
233 
234 void PointViewerWidget::ClearLocations() {
235  while (location_table_->rowCount() > 0) {
236  location_table_->removeRow(0);
237  }
238  for (auto location_label : location_labels_) {
239  delete location_label;
240  }
241  location_labels_.clear();
242 }
243 
244 void PointViewerWidget::UpdateImages() {
245  ClearLocations();
246 
247  location_table_->setRowCount(static_cast<int>(location_pixmaps_.size()));
248 
249  for (size_t i = 0; i < location_pixmaps_.size(); ++i) {
250  QLabel* image_id_label =
251  new QLabel(QString::number(image_ids_[i]), this);
252  image_id_label->setAlignment(Qt::AlignCenter);
253  location_table_->setCellWidget(i, 0, image_id_label);
254  location_labels_.push_back(image_id_label);
255 
256  QLabel* error_label =
257  new QLabel(QString::number(reproj_errors_[i]), this);
258  error_label->setAlignment(Qt::AlignCenter);
259  location_table_->setCellWidget(i, 1, error_label);
260  location_labels_.push_back(error_label);
261 
262  const QPixmap& pixmap = location_pixmaps_[i];
263  QLabel* image_label = new QLabel(this);
264  image_label->setPixmap(pixmap.scaledToWidth(zoom_ * pixmap.width(),
265  Qt::FastTransformation));
266  location_table_->setCellWidget(i, 2, image_label);
267  location_table_->resizeRowToContents(i);
268  location_labels_.push_back(image_label);
269 
270  QLabel* image_name_label = new QLabel(image_names_[i].c_str(), this);
271  image_name_label->setAlignment(Qt::AlignCenter);
272  location_table_->setCellWidget(i, 3, image_name_label);
273  location_labels_.push_back(image_name_label);
274  }
275  location_table_->resizeColumnToContents(2);
276 }
277 
278 void PointViewerWidget::ZoomIn() {
279  zoom_ *= 1.33;
280  UpdateImages();
281 }
282 
283 void PointViewerWidget::ZoomOut() {
284  zoom_ /= 1.3;
285  UpdateImages();
286 }
287 
288 void PointViewerWidget::Delete() {
289  QMessageBox::StandardButton reply = QMessageBox::question(
290  this, "", tr("Do you really want to delete this point?"),
291  QMessageBox::Yes | QMessageBox::No);
292  if (reply == QMessageBox::Yes) {
293  if (model_viewer_widget_->reconstruction->ExistsPoint3D(point3D_id_)) {
294  model_viewer_widget_->reconstruction->DeletePoint3D(point3D_id_);
295  }
296  model_viewer_widget_->ReloadReconstruction();
297  }
298 }
299 
300 } // namespace cloudViewer
MouseEvent event
std::shared_ptr< core::Tensor > image
int height
std::unordered_map< colmap::point3D_t, colmap::Point3D > points3D
std::unordered_map< colmap::camera_t, colmap::Camera > cameras
colmap::Reconstruction * reconstruction
std::unordered_map< colmap::image_t, colmap::Image > images
void Show(const colmap::point3D_t point3D_id)
PointViewerWidget(QWidget *parent, ModelViewerWidget *model_viewer_widget, OptionManager *option)
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
Tensor Minimum(const Tensor &input, const Tensor &other)
Computes the element-wise minimum of input and other. The tensors must have same data type and device...
static const std::string path
Definition: PointCloud.cpp:59
std::string JoinPaths(T const &...paths)
Definition: FileSystem.h:23
Generic file read and write utility for python interface.
colmap::OptionManager OptionManager
constexpr Rgb red(MAX, 0, 0)
constexpr Rgb green(0, MAX, 0)
std::string to_string(const T &n)
Definition: Common.h:20
struct Window Window
Definition: sqlite3.c:14678