Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement saving/loading different formats in g2o_viewer #820

Merged
merged 1 commit into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 76 additions & 11 deletions g2o/apps/g2o_viewer/main_window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
#include <QDoubleValidator>
#include <QFileDialog>
#include <QStandardItemModel>
#include <algorithm>
#include <cassert>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>

#include "g2o/core/estimate_propagator.h"
Expand All @@ -40,10 +42,56 @@
#include "properties_widget.h"
#include "viewer_properties_widget.h"

namespace {
/**
* @brief Join into a string using a delimeter
*
* @tparam Iterator
* @tparam std::iterator_traits<Iterator>::value_type
* @param b begin of the range for output
* @param e end of the range for output
* @param delimiter will be inserted in between elements
* @return std::string joined string
*/
template <typename Iterator,
typename Value = typename std::iterator_traits<Iterator>::value_type>
std::string strJoin(Iterator b, Iterator e, const std::string& delimiter) {
std::ostringstream os;
if (b != e) {
std::copy(b, std::prev(e),
std::ostream_iterator<Value>(os, delimiter.c_str()));
b = std::prev(e);
}
if (b != e) {
os << *b;
}
return os.str();
}

QString prepareFilter(const std::vector<g2o::io::FileFilter>& filters) {
std::vector<std::string> filter_patterns;
filter_patterns.reserve(filters.size());
std::transform(filters.begin(), filters.end(),
std::back_inserter(filter_patterns),
[](const g2o::io::FileFilter& f) { return f.filter; });
return QString::fromStdString(
strJoin(filter_patterns.begin(), filter_patterns.end(), ";;"));
}

g2o::io::Format extractFileFormat(
const std::vector<g2o::io::FileFilter>& filters,
const std::string& file_pattern) {
for (const auto& f : filters) {
if (f.filter == file_pattern) return f.format;
}
return g2o::io::Format::kUndefined;
}
} // namespace

MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
setupUi(this);
leKernelWidth->setValidator(
new QDoubleValidator(-std::numeric_limits<double>::max(),
new QDoubleValidator(std::numeric_limits<double>::lowest(),
std::numeric_limits<double>::max(), 7, this));
plainTextEdit->setMaximumBlockCount(1000);
btnForceStop->hide();
Expand All @@ -52,19 +100,34 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
}

void MainWindow::on_actionLoad_triggered(bool) {
const std::vector<g2o::io::FileFilter> file_filters =
g2o::io::getFileFilter(true /*open*/);
QString selectedFilter;
const QString filename = QFileDialog::getOpenFileName(
this, "Load g2o file", "", "g2o files (*.g2o);;All Files (*)");
this, "Load g2o file", "", prepareFilter(file_filters), &selectedFilter);
if (!filename.isNull()) {
loadFromFile(filename);
const g2o::io::Format file_format =
extractFileFormat(file_filters, selectedFilter.toStdString());
G2O_DEBUG("Selected filter {} with format {}", selectedFilter.toStdString(),
g2o::io::to_string(file_format));
loadFromFile(filename, file_format);
}
}

void MainWindow::on_actionSave_triggered(bool) {
const std::vector<g2o::io::FileFilter> file_filters =
g2o::io::getFileFilter(false /*open*/);
QString selectedFilter;

const QString filename = QFileDialog::getSaveFileName(
this, "Save g2o file", "", "g2o files (*.g2o)");
this, "Save g2o file", "", prepareFilter(file_filters), &selectedFilter);
if (!filename.isNull()) {
const g2o::io::Format file_format =
extractFileFormat(file_filters, selectedFilter.toStdString());
G2O_DEBUG("Selected filter {} with format {}", selectedFilter.toStdString(),
g2o::io::to_string(file_format));
std::ofstream fout(filename.toStdString().c_str());
viewer->graph->save(fout);
viewer->graph->save(fout, file_format);
if (fout.good())
std::cerr << "Saved " << filename.toStdString() << '\n';
else
Expand Down Expand Up @@ -217,18 +280,20 @@ void MainWindow::updateDisplayedSolvers() {
}
}

bool MainWindow::load(const QString& filename) {
bool MainWindow::load(const QString& filename, g2o::io::Format format) {
viewer->graph->clear();
bool loadStatus = false;
if (filename == "-") {
std::cerr << "reading stdin\n";
loadStatus = viewer->graph->load(std::cin);
loadStatus = viewer->graph->load(std::cin, format);
} else {
const std::string filename_as_std = filename.toStdString();
const std::string filename_extension =
g2o::getFileExtension(filename_as_std);
std::optional<g2o::io::Format> file_format =
g2o::io::formatForFileExtension(filename_extension);
const std::optional<g2o::io::Format> file_format =
format != g2o::io::Format::kUndefined
? format
: g2o::io::formatForFileExtension(filename_extension);
std::ifstream ifs(filename_as_std);
if (!ifs) return false;
loadStatus = file_format.has_value()
Expand Down Expand Up @@ -346,9 +411,9 @@ void MainWindow::setRobustKernel() {

void MainWindow::on_btnForceStop_clicked() { forceStopFlag_ = true; }

bool MainWindow::loadFromFile(const QString& filename) {
bool MainWindow::loadFromFile(const QString& filename, g2o::io::Format format) {
viewer->graph->clear();
bool loadStatus = load(filename);
bool loadStatus = load(filename, format);
if (loadStatus) {
filename_ = filename.toStdString();
}
Expand Down
7 changes: 5 additions & 2 deletions g2o/apps/g2o_viewer/main_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <vector>

#include "g2o/core/io/io_format.h"
#include "g2o/core/optimization_algorithm_property.h"
#include "g2o_viewer_api.h"
#include "ui_base_main_window.h"
Expand Down Expand Up @@ -56,7 +57,8 @@ class G2O_VIEWER_API MainWindow : public QMainWindow,
/**
* load a graph on which we will operate from a file
*/
bool loadFromFile(const QString& filename);
bool loadFromFile(const QString& filename,
g2o::io::Format format = g2o::io::Format::kUndefined);

public slots: // NOLINT
void on_actionLoad_triggered(bool);
Expand All @@ -80,7 +82,8 @@ class G2O_VIEWER_API MainWindow : public QMainWindow,
bool allocateSolver(bool& allocatedNewSolver);
bool prepare();
void setRobustKernel();
bool load(const QString& filename);
bool load(const QString& filename,
g2o::io::Format format = g2o::io::Format::kUndefined);

std::vector<g2o::OptimizationAlgorithmProperty> knownSolvers_;
int lastSolver_ = -1;
Expand Down
21 changes: 4 additions & 17 deletions g2o/core/abstract_graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,6 @@
#include "io/io_g2o.h"

namespace {
#ifdef G2O_HAVE_LOGGING
std::string_view to_string(g2o::io::Format format) {
switch (format) {
case g2o::io::Format::kG2O:
return "G2O";
case g2o::io::Format::kBinary:
return "Binary";
case g2o::io::Format::kJson:
return "JSON";
case g2o::io::Format::kXML:
return "XML";
}
return "";
}
#endif

/**
* @brief Allocate an g2o::IoInterface to load/save data of the graph.
*
Expand All @@ -61,6 +45,9 @@
*/
std::unique_ptr<g2o::IoInterface> allocate(g2o::io::Format format) {
switch (format) {
case g2o::io::Format::kUndefined:
G2O_WARN("Cannot allocate IO interface for undefined format");
return nullptr;

Check warning on line 50 in g2o/core/abstract_graph.cpp

View check run for this annotation

Codecov / codecov/patch

g2o/core/abstract_graph.cpp#L48-L50

Added lines #L48 - L50 were not covered by tests
case g2o::io::Format::kG2O:
return std::make_unique<g2o::IoG2O>();
case g2o::io::Format::kBinary:
Expand All @@ -71,7 +58,7 @@
return std::make_unique<g2o::IoXml>();
}
G2O_CRITICAL("Failed to create graph IO interface for format {}",
to_string(format));
g2o::io::to_string(format));
return nullptr;
}

Expand Down
35 changes: 35 additions & 0 deletions g2o/core/io/io_format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,22 @@

namespace g2o::io {

std::string_view to_string(g2o::io::Format format) {
switch (format) {
case g2o::io::Format::kUndefined:
return "Undefined";
case g2o::io::Format::kG2O:
return "G2O";
case g2o::io::Format::kBinary:
return "Binary";
case g2o::io::Format::kJson:
return "JSON";
case g2o::io::Format::kXML:
return "XML";
}
return "";
}

std::optional<Format> formatForFileExtension(std::string_view extension) {
if (extension == "g2o" || extension == "G2O") return Format::kG2O;
if (extension == "json" || extension == "JSON") return Format::kJson;
Expand All @@ -39,4 +55,23 @@ std::optional<Format> formatForFileExtension(std::string_view extension) {
return std::nullopt;
}

FileFilter::FileFilter(std::string filter, Format format)
: filter(std::move(filter)), format(format) {}

bool FileFilter::operator==(const FileFilter& other) const {
return filter == other.filter && format == other.format;
}

std::vector<FileFilter> getFileFilter(bool open) {
std::vector<FileFilter> result;
result.emplace_back("g2o Ascii files (*.g2o)", Format::kG2O);
result.emplace_back("g2o Json files (*.json)", Format::kJson);
result.emplace_back("g2o XML files (*.xml)", Format::kXML);
result.emplace_back("g2o BIN files (*.bin)", Format::kBinary);
if (open) {
result.emplace_back("All Files (*)", Format::kUndefined);
}
return result;
}

} // namespace g2o::io
31 changes: 30 additions & 1 deletion g2o/core/io/io_format.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,33 @@
#define G2O_CORE_IO_FORMAT_H

#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "g2o/core/g2o_core_api.h"
namespace g2o::io {

enum class G2O_CORE_API Format { kG2O = 0, kBinary = 1, kJson = 2, kXML = 3 };
enum class G2O_CORE_API Format {
kUndefined = -1,
kG2O = 0,
kBinary = 1,
kJson = 2,
kXML = 3
};

G2O_CORE_API std::string_view to_string(g2o::io::Format format);

/**
* @brief FileFilter information for Open/Save Dialog
*
*/
struct G2O_CORE_API FileFilter {
FileFilter(std::string filter, Format format);
std::string filter; ///< filter string
Format format; ///< IO format
bool operator==(const FileFilter& other) const;
};

/**
* @brief Maps a file extension to a format value
Expand All @@ -45,6 +66,14 @@ enum class G2O_CORE_API Format { kG2O = 0, kBinary = 1, kJson = 2, kXML = 3 };
G2O_CORE_API std::optional<Format> formatForFileExtension(
std::string_view extension);

/**
* @brief Get the filters for file open dialogs.
*
* @param open true, if opening files, false for save
* @return std::vector<std::string> Filters
*/
G2O_CORE_API std::vector<FileFilter> getFileFilter(bool open);

} // namespace g2o::io

#endif
18 changes: 18 additions & 0 deletions unit_test/general/graph_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,21 @@ const auto kFileformatsToTest = Values(
INSTANTIATE_TEST_SUITE_P(AbstractGraph, AbstractGraphIO, kFileformatsToTest);
INSTANTIATE_TEST_SUITE_P(OptimizableGraphGraph, OptimizableGraphIO,
kFileformatsToTest);

TEST(OptimizableGraphIO, FileFilter) {
EXPECT_THAT(g2o::io::getFileFilter(false), Not(IsEmpty()));
EXPECT_THAT(g2o::io::getFileFilter(true), Not(IsEmpty()));
EXPECT_THAT(g2o::io::getFileFilter(false),
IsSubsetOf(g2o::io::getFileFilter(true)));
}

TEST(OptimizableGraphIO, FormatToString) {
static constexpr g2o::io::Format kAllFormats[] = {
g2o::io::Format::kUndefined, g2o::io::Format::kG2O,
g2o::io::Format::kBinary, g2o::io::Format::kJson, g2o::io::Format::kXML};
for (const auto& f : kAllFormats)
EXPECT_THAT(g2o::io::to_string(f), Not(IsEmpty()));

EXPECT_THAT(g2o::io::to_string(static_cast<g2o::io::Format>(1 << 31)),
IsEmpty());
}
Loading