Skip to content

Commit

Permalink
Implement saving/loading different formats in g2o_viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
RainerKuemmerle committed Jul 27, 2024
1 parent f449b3d commit a31323a
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 31 deletions.
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::string_view to_string(g2o::io::Format format) {
*/
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 @@ std::unique_ptr<g2o::IoInterface> allocate(g2o::io::Format format) {
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());
}

0 comments on commit a31323a

Please sign in to comment.