// Copyright (c) 2022, ETH Zurich and UNC Chapel Hill. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // * Neither the name of ETH Zurich and UNC Chapel Hill nor the names of // its contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // Author: Johannes L. Schoenberger (jsch-at-demuc-dot-de) #include "ui/image_viewer_widget.h" #include "ui/model_viewer_widget.h" #include "util/misc.h" namespace colmap { const double ImageViewerWidget::kZoomFactor = 1.20; ImageViewerGraphicsScene::ImageViewerGraphicsScene() { setSceneRect(0, 0, 0, 0); image_pixmap_item_ = addPixmap(QPixmap::fromImage(QImage())); image_pixmap_item_->setZValue(-1); } QGraphicsPixmapItem* ImageViewerGraphicsScene::ImagePixmapItem() const { return image_pixmap_item_; } ImageViewerWidget::ImageViewerWidget(QWidget* parent) : QWidget(parent) { setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); resize(parent->width() - 20, parent->height() - 20); QFont font; font.setPointSize(10); setFont(font); grid_layout_ = new QGridLayout(this); grid_layout_->setContentsMargins(5, 5, 5, 5); graphics_view_ = new QGraphicsView(); graphics_view_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); graphics_view_->setScene(&graphics_scene_); graphics_view_->setAlignment(Qt::AlignLeft | Qt::AlignTop); grid_layout_->addWidget(graphics_view_, 1, 0); button_layout_ = new QHBoxLayout(); QPushButton* zoom_in_button = new QPushButton("+", this); zoom_in_button->setFont(font); zoom_in_button->setFixedWidth(50); button_layout_->addWidget(zoom_in_button); connect(zoom_in_button, &QPushButton::released, this, &ImageViewerWidget::ZoomIn); QPushButton* zoom_out_button = new QPushButton("-", this); zoom_out_button->setFont(font); zoom_out_button->setFixedWidth(50); button_layout_->addWidget(zoom_out_button); connect(zoom_out_button, &QPushButton::released, this, &ImageViewerWidget::ZoomOut); QPushButton* save_button = new QPushButton("Save image", this); save_button->setFont(font); button_layout_->addWidget(save_button); connect(save_button, &QPushButton::released, this, &ImageViewerWidget::Save); grid_layout_->addLayout(button_layout_, 2, 0, Qt::AlignRight); } void ImageViewerWidget::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); graphics_view_->fitInView(graphics_scene_.sceneRect(), Qt::KeepAspectRatio); } void ImageViewerWidget::closeEvent(QCloseEvent* event) { graphics_scene_.ImagePixmapItem()->setPixmap(QPixmap()); } void ImageViewerWidget::ShowBitmap(const Bitmap& bitmap) { ShowPixmap(QPixmap::fromImage(BitmapToQImageRGB(bitmap))); } void ImageViewerWidget::ShowPixmap(const QPixmap& pixmap) { graphics_scene_.ImagePixmapItem()->setPixmap(pixmap); graphics_scene_.setSceneRect(pixmap.rect()); show(); graphics_view_->fitInView(graphics_scene_.sceneRect(), Qt::KeepAspectRatio); raise(); } void ImageViewerWidget::ReadAndShow(const std::string& path) { Bitmap bitmap; if (!bitmap.Read(path, true)) { std::cerr << "ERROR: Cannot read image at path " << path << std::endl; } ShowBitmap(bitmap); } void ImageViewerWidget::ZoomIn() { graphics_view_->scale(kZoomFactor, kZoomFactor); } void ImageViewerWidget::ZoomOut() { graphics_view_->scale(1.0 / kZoomFactor, 1.0 / kZoomFactor); } void ImageViewerWidget::Save() { QString filter("PNG (*.png)"); const QString save_path = QFileDialog::getSaveFileName(this, tr("Select destination..."), "", "PNG (*.png);;JPEG (*.jpg);;BMP (*.bmp)", &filter) .toUtf8() .constData(); // Selection canceled? if (save_path == "") { return; } graphics_scene_.ImagePixmapItem()->pixmap().save(save_path); } FeatureImageViewerWidget::FeatureImageViewerWidget( QWidget* parent, const std::string& switch_text) : ImageViewerWidget(parent), switch_state_(true), switch_text_(switch_text) { switch_button_ = new QPushButton(tr(("Hide " + switch_text_).c_str()), this); switch_button_->setFont(font()); button_layout_->addWidget(switch_button_); connect(switch_button_, &QPushButton::released, this, &FeatureImageViewerWidget::ShowOrHide); } void FeatureImageViewerWidget::ReadAndShowWithKeypoints( const std::string& path, const FeatureKeypoints& keypoints, const std::vector& tri_mask) { Bitmap bitmap; if (!bitmap.Read(path, true)) { std::cerr << "ERROR: Cannot read image at path " << path << std::endl; } image1_ = QPixmap::fromImage(BitmapToQImageRGB(bitmap)); image2_ = image1_; const size_t num_tri_keypoints = std::count_if( tri_mask.begin(), tri_mask.end(), [](const bool tri) { return tri; }); FeatureKeypoints keypoints_tri(num_tri_keypoints); FeatureKeypoints keypoints_not_tri(keypoints.size() - num_tri_keypoints); size_t i_tri = 0; size_t i_not_tri = 0; for (size_t i = 0; i < tri_mask.size(); ++i) { if (tri_mask[i]) { keypoints_tri[i_tri] = keypoints[i]; i_tri += 1; } else { keypoints_not_tri[i_not_tri] = keypoints[i]; i_not_tri += 1; } } DrawKeypoints(&image2_, keypoints_tri, Qt::magenta); DrawKeypoints(&image2_, keypoints_not_tri, Qt::red); if (switch_state_) { ShowPixmap(image2_); } else { ShowPixmap(image1_); } } void FeatureImageViewerWidget::ReadAndShowWithMatches( const std::string& path1, const std::string& path2, const FeatureKeypoints& keypoints1, const FeatureKeypoints& keypoints2, const FeatureMatches& matches) { Bitmap bitmap1; Bitmap bitmap2; if (!bitmap1.Read(path1, true) || !bitmap2.Read(path2, true)) { std::cerr << "ERROR: Cannot read images at paths " << path1 << " and " << path2 << std::endl; return; } const auto image1 = QPixmap::fromImage(BitmapToQImageRGB(bitmap1)); const auto image2 = QPixmap::fromImage(BitmapToQImageRGB(bitmap2)); image1_ = ShowImagesSideBySide(image1, image2); image2_ = DrawMatches(image1, image2, keypoints1, keypoints2, matches); if (switch_state_) { ShowPixmap(image2_); } else { ShowPixmap(image1_); } } void FeatureImageViewerWidget::ShowOrHide() { if (switch_state_) { switch_button_->setText(std::string("Show " + switch_text_).c_str()); ShowPixmap(image1_); switch_state_ = false; } else { switch_button_->setText(std::string("Hide " + switch_text_).c_str()); ShowPixmap(image2_); switch_state_ = true; } } DatabaseImageViewerWidget::DatabaseImageViewerWidget( QWidget* parent, ModelViewerWidget* model_viewer_widget, OptionManager* options) : FeatureImageViewerWidget(parent, "keypoints"), model_viewer_widget_(model_viewer_widget), options_(options) { setWindowTitle("Image information"); table_widget_ = new QTableWidget(this); table_widget_->setColumnCount(2); table_widget_->setRowCount(11); QFont font; font.setPointSize(10); table_widget_->setFont(font); table_widget_->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); table_widget_->setEditTriggers(QAbstractItemView::NoEditTriggers); table_widget_->setSelectionMode(QAbstractItemView::SingleSelection); table_widget_->setShowGrid(true); table_widget_->horizontalHeader()->setStretchLastSection(true); table_widget_->horizontalHeader()->setVisible(false); table_widget_->verticalHeader()->setVisible(false); table_widget_->verticalHeader()->setDefaultSectionSize(18); int table_row = 0; table_widget_->setItem(table_row, 0, new QTableWidgetItem("image_id")); image_id_item_ = new QTableWidgetItem(); table_widget_->setItem(table_row, 1, image_id_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("camera_id")); camera_id_item_ = new QTableWidgetItem(); table_widget_->setItem(table_row, 1, camera_id_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("camera_model")); camera_model_item_ = new QTableWidgetItem(); table_widget_->setItem(table_row, 1, camera_model_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("camera_params")); camera_params_item_ = new QTableWidgetItem(); table_widget_->setItem(table_row, 1, camera_params_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("qw, qx, qy, qz")); qvec_item_ = new QTableWidgetItem(); table_widget_->setItem(table_row, 1, qvec_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("tx, ty, tz")); tvec_item_ = new QTableWidgetItem(); table_widget_->setItem(table_row, 1, tvec_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("dims")); dimensions_item_ = new QTableWidgetItem(); table_widget_->setItem(table_row, 1, dimensions_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("num_points2D")); num_points2D_item_ = new QTableWidgetItem(); num_points2D_item_->setForeground(Qt::red); table_widget_->setItem(table_row, 1, num_points2D_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("num_points3D")); num_points3D_item_ = new QTableWidgetItem(); num_points3D_item_->setForeground(Qt::magenta); table_widget_->setItem(table_row, 1, num_points3D_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("num_observations")); num_obs_item_ = new QTableWidgetItem(); table_widget_->setItem(table_row, 1, num_obs_item_); table_row += 1; table_widget_->setItem(table_row, 0, new QTableWidgetItem("name")); name_item_ = new QTableWidgetItem(); table_widget_->setItem(table_row, 1, name_item_); table_row += 1; grid_layout_->addWidget(table_widget_, 0, 0); delete_button_ = new QPushButton(tr("Delete"), this); delete_button_->setFont(font); button_layout_->addWidget(delete_button_); connect(delete_button_, &QPushButton::released, this, &DatabaseImageViewerWidget::DeleteImage); } void DatabaseImageViewerWidget::ShowImageWithId(const image_t image_id) { if (model_viewer_widget_->images.count(image_id) == 0) { return; } image_id_ = image_id; const Image& image = model_viewer_widget_->images.at(image_id); const Camera& camera = model_viewer_widget_->cameras.at(image.CameraId()); image_id_item_->setText(QString::number(image_id)); camera_id_item_->setText(QString::number(image.CameraId())); camera_model_item_->setText(QString::fromStdString(camera.ModelName())); camera_params_item_->setText(QString::fromStdString(camera.ParamsToString())); qvec_item_->setText(QString::number(image.Qvec(0)) + ", " + QString::number(image.Qvec(1)) + ", " + QString::number(image.Qvec(2)) + ", " + QString::number(image.Qvec(3))); tvec_item_->setText(QString::number(image.Tvec(0)) + ", " + QString::number(image.Tvec(1)) + ", " + QString::number(image.Tvec(2))); dimensions_item_->setText(QString::number(camera.Width()) + "x" + QString::number(camera.Height())); num_points2D_item_->setText(QString::number(image.NumPoints2D())); std::vector tri_mask(image.NumPoints2D()); for (size_t i = 0; i < image.NumPoints2D(); ++i) { tri_mask[i] = image.Point2D(i).HasPoint3D(); } num_points3D_item_->setText(QString::number(image.NumPoints3D())); num_obs_item_->setText(QString::number(image.NumObservations())); name_item_->setText(QString::fromStdString(image.Name())); ResizeTable(); FeatureKeypoints keypoints(image.NumPoints2D()); for (point2D_t i = 0; i < image.NumPoints2D(); ++i) { keypoints[i].x = static_cast(image.Point2D(i).X()); keypoints[i].y = static_cast(image.Point2D(i).Y()); } const std::string path = JoinPaths(*options_->image_path, image.Name()); ReadAndShowWithKeypoints(path, keypoints, tri_mask); } void DatabaseImageViewerWidget::ResizeTable() { // Set fixed table dimensions. table_widget_->resizeColumnsToContents(); int height = table_widget_->horizontalHeader()->height() + 2 * table_widget_->frameWidth(); for (int i = 0; i < table_widget_->rowCount(); i++) { height += table_widget_->rowHeight(i); } table_widget_->setFixedHeight(height); } void DatabaseImageViewerWidget::DeleteImage() { QMessageBox::StandardButton reply = QMessageBox::question( this, "", tr("Do you really want to delete this image?"), QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { if (model_viewer_widget_->reconstruction->ExistsImage(image_id_)) { model_viewer_widget_->reconstruction->DeRegisterImage(image_id_); } model_viewer_widget_->ReloadReconstruction(); } hide(); } } // namespace colmap