File main_window.cpp
File List > gui > main_window.cpp
Go to the documentation of this file
#include "main_window.hpp"
#include <QtConcurrent>
#include <QtWidgets>
#include <cstdlib>
#include "config_input/config_input.hpp"
#include "error_dialog.hpp"
#include "progress_box.hpp"
#include "run.hpp"
#include "ui_main_window.h"
#include "utilities/env.hpp"
MainWindow::MainWindow(const std::vector<fs::path>& initial_las_files)
: ui(std::make_unique<Ui::MainWindow>()) {
try {
if (!QIcon::hasThemeIcon("list-add")) {
QIcon::setThemeName("Humanity");
QIcon::setFallbackThemeName("default");
}
ui->setupUi(this);
if (!initial_las_files.empty()) {
ui->config_editor->set_las_files(initial_las_files);
}
connect(ui->runButton, &QPushButton::clicked, this, &MainWindow::run_blaze);
connect(ui->actionOpen, &QAction::triggered, this,
[this] { ui->config_editor->open_config_file(); });
connect(ui->actionSaveAs, &QAction::triggered, this,
[this] { ui->config_editor->save_config_file(); });
connect(ui->actionResetDefaults, &QAction::triggered, this,
[this] { ui->config_editor->reset_to_defaults(); });
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::about);
connect(ui->config_editor, &ConfigEditor::config_changed,
[this] { ui->runButton->setEnabled(ui->config_editor->is_valid()); });
ui->runButton->setEnabled(ui->config_editor->is_valid());
} catch (const std::exception& e) {
show_error_message(this, "Error", e.what());
exit(1);
}
}
MainWindow::~MainWindow() {}
void MainWindow::about() {
QMessageBox::about(this, "About Blaze",
QString("This is the Blaze LIDAR mapping tool. It is free and open-source, "
"released under the GPLv3 license.<br><br>"
"Please contact us at <a "
"href=\"mailto:trailblaze.software@gmail.com\">trailblaze.software@"
"gmail.com</a> for other licensing options.<br><br>"
"Version: " BLAZE_VERSION "<br>"
"Commit hash: " GIT_COMMIT_HASH "<br><br>"
"<a href=\"https://github.com/Trailblaze-Software/Blaze\">Source "
"code</a> available.<br>"));
}
void MainWindow::run_blaze() {
const Config& config = ui->config_editor->get_config();
// Warn if the requested bin resolution will leave fewer than 5 LiDAR points
// per bin on average. At that density ground/vegetation classifications and
// contours tend to become noisy; it usually indicates the user either picked
// too fine a bin resolution or loaded too little data for the area.
constexpr double MIN_POINTS_PER_BIN = 5.0;
const double total_area = ui->config_editor->last_total_area_m2();
const std::uint64_t total_points = ui->config_editor->last_total_points();
const double bin_res = config.grid.bin_resolution;
if (total_area > 0.0 && total_points > 0 && bin_res > 0.0) {
const double density = static_cast<double>(total_points) / total_area; // pts/m^2
const double points_per_bin = density * bin_res * bin_res;
if (points_per_bin < MIN_POINTS_PER_BIN) {
QMessageBox box(this);
box.setIcon(QMessageBox::Warning);
box.setWindowTitle("Low point density");
box.setText(QString("Average density is only %1 point(s) per %2 m bin "
"(below the recommended minimum of %3).")
.arg(points_per_bin, 0, 'f', 2)
.arg(bin_res, 0, 'f', 2)
.arg(MIN_POINTS_PER_BIN, 0, 'f', 0));
box.setInformativeText(QString("Overall: %1 points over %2 km\u00B2 (%3 pts/m\u00B2).\n\n"
"Consider increasing \"Bin Resolution\" on the General tab, "
"or adding more LAS/LAZ data, before processing. Continue "
"anyway?")
.arg(QLocale().toString(static_cast<qulonglong>(total_points)))
.arg(total_area / 1'000'000.0, 0, 'f', 3)
.arg(density, 0, 'f', 2));
box.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
box.setDefaultButton(QMessageBox::Cancel);
box.button(QMessageBox::Yes)->setText("Run anyway");
if (box.exec() != QMessageBox::Yes) {
return;
}
}
}
// When Blaze is launched by Blaze3D it sets BLAZE_EXIT_AFTER_RUN so that the
// window closes itself once processing is done. In that headless mode we must
// exit on *both* success and failure (with a distinct exit code), and we must
// not pop a blocking modal dialog, otherwise the window would stay open
// waiting for the user to dismiss it and Blaze3D could never tell whether the
// run actually succeeded.
const bool exit_after_run = blaze::get_env("BLAZE_EXIT_AFTER_RUN") != nullptr;
ProgressBox* message_box = new ProgressBox(this);
message_box->show();
message_box->start_task(
[&config, message_box] {
run_with_config(config, std::vector<fs::path>(), ProgressTracker(message_box));
},
[&config, exit_after_run] {
if (exit_after_run) {
QApplication::exit(0);
return;
}
QDialog* dialog = new QDialog();
dialog->setWindowTitle("Blaze processing done!");
QVBoxLayout* layout = new QVBoxLayout();
dialog->setLayout(layout);
QLabel* label = new QLabel("Processing complete!");
layout->addWidget(label);
if (config.processing_steps.contains(ProcessingStep::Combine)) {
QLabel* label2 =
new QLabel("The combined output is located at: " +
QString::fromStdString((config.output_path() / "combined").string()));
layout->addWidget(label2);
QImage image((config.output_path() / "combined" / "final_img.tif").string().c_str());
if (image.isNull()) {
std::cerr << "Failed to load image from "
<< (config.output_path() / "combined" / "final_img.tif") << std::endl;
} else {
QScrollArea* scroll_area = new QScrollArea();
layout->addWidget(scroll_area);
QLabel* image_label = new QLabel();
image_label->setPixmap(QPixmap::fromImage(image));
scroll_area->setWidget(image_label);
scroll_area->setWidgetResizable(true);
}
}
QPushButton* button = new QPushButton("OK");
layout->addWidget(button);
connect(button, &QPushButton::clicked, dialog, &QDialog::accept);
dialog->exec();
},
[this, exit_after_run](const QString& message) {
if (exit_after_run) {
// Headless run: report on stderr (captured by Blaze3D) and exit with a
// non-zero code so the launching process knows the run failed instead
// of silently finding no output.
std::cerr << "Blaze processing failed: " << message.toStdString() << std::endl;
QApplication::exit(1);
return;
}
show_error_message(this, "Error running task", message);
});
}