Test LoadState and pure localization (#1190)
parent
5055703490
commit
29f6ea9ea3
|
@ -41,6 +41,16 @@ ResolveLuaParameters(const std::string& lua_code) {
|
||||||
std::vector<cartographer::sensor::TimedPointCloudData>
|
std::vector<cartographer::sensor::TimedPointCloudData>
|
||||||
GenerateFakeRangeMeasurements(double travel_distance, double duration,
|
GenerateFakeRangeMeasurements(double travel_distance, double duration,
|
||||||
double time_step) {
|
double time_step) {
|
||||||
|
const Eigen::Vector3f kDirection = Eigen::Vector3f(2., 1., 0.).normalized();
|
||||||
|
return GenerateFakeRangeMeasurements(kDirection * travel_distance, duration,
|
||||||
|
time_step,
|
||||||
|
transform::Rigid3f::Identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<cartographer::sensor::TimedPointCloudData>
|
||||||
|
GenerateFakeRangeMeasurements(const Eigen::Vector3f& translation,
|
||||||
|
double duration, double time_step,
|
||||||
|
const transform::Rigid3f& local_to_global) {
|
||||||
std::vector<cartographer::sensor::TimedPointCloudData> measurements;
|
std::vector<cartographer::sensor::TimedPointCloudData> measurements;
|
||||||
cartographer::sensor::TimedPointCloud point_cloud;
|
cartographer::sensor::TimedPointCloud point_cloud;
|
||||||
for (double angle = 0.; angle < M_PI; angle += 0.01) {
|
for (double angle = 0.; angle < M_PI; angle += 0.01) {
|
||||||
|
@ -50,18 +60,18 @@ GenerateFakeRangeMeasurements(double travel_distance, double duration,
|
||||||
kRadius * std::sin(angle), height, 0.);
|
kRadius * std::sin(angle), height, 0.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Eigen::Vector3f kDirection = Eigen::Vector3f(2., 1., 0.).normalized();
|
const Eigen::Vector3f kVelocity = translation / duration;
|
||||||
const Eigen::Vector3f kVelocity = travel_distance / duration * kDirection;
|
|
||||||
for (double elapsed_time = 0.; elapsed_time < duration;
|
for (double elapsed_time = 0.; elapsed_time < duration;
|
||||||
elapsed_time += time_step) {
|
elapsed_time += time_step) {
|
||||||
cartographer::common::Time time =
|
cartographer::common::Time time =
|
||||||
cartographer::common::FromUniversal(123) +
|
cartographer::common::FromUniversal(123) +
|
||||||
cartographer::common::FromSeconds(elapsed_time);
|
cartographer::common::FromSeconds(elapsed_time);
|
||||||
cartographer::transform::Rigid3f pose =
|
cartographer::transform::Rigid3f global_pose =
|
||||||
|
local_to_global *
|
||||||
cartographer::transform::Rigid3f::Translation(elapsed_time * kVelocity);
|
cartographer::transform::Rigid3f::Translation(elapsed_time * kVelocity);
|
||||||
cartographer::sensor::TimedPointCloud ranges =
|
cartographer::sensor::TimedPointCloud ranges =
|
||||||
cartographer::sensor::TransformTimedPointCloud(point_cloud,
|
cartographer::sensor::TransformTimedPointCloud(point_cloud,
|
||||||
pose.inverse());
|
global_pose.inverse());
|
||||||
measurements.emplace_back(cartographer::sensor::TimedPointCloudData{
|
measurements.emplace_back(cartographer::sensor::TimedPointCloudData{
|
||||||
time, Eigen::Vector3f::Zero(), ranges});
|
time, Eigen::Vector3f::Zero(), ranges});
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,11 @@ std::vector<cartographer::sensor::TimedPointCloudData>
|
||||||
GenerateFakeRangeMeasurements(double travel_distance, double duration,
|
GenerateFakeRangeMeasurements(double travel_distance, double duration,
|
||||||
double time_step);
|
double time_step);
|
||||||
|
|
||||||
|
std::vector<cartographer::sensor::TimedPointCloudData>
|
||||||
|
GenerateFakeRangeMeasurements(const Eigen::Vector3f& translation,
|
||||||
|
double duration, double time_step,
|
||||||
|
const transform::Rigid3f& local_to_global);
|
||||||
|
|
||||||
proto::Submap CreateFakeSubmap3D(int trajectory_id = 1, int submap_index = 1);
|
proto::Submap CreateFakeSubmap3D(int trajectory_id = 1, int submap_index = 1);
|
||||||
|
|
||||||
proto::Node CreateFakeNode(int trajectory_id = 1, int node_index = 1);
|
proto::Node CreateFakeNode(int trajectory_id = 1, int node_index = 1);
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
#include "cartographer/mapping/map_builder.h"
|
#include "cartographer/mapping/map_builder.h"
|
||||||
|
|
||||||
#include "cartographer/common/config.h"
|
#include "cartographer/common/config.h"
|
||||||
|
#include "cartographer/io/proto_stream.h"
|
||||||
#include "cartographer/mapping/internal/testing/test_helpers.h"
|
#include "cartographer/mapping/internal/testing/test_helpers.h"
|
||||||
|
#include "gmock/gmock.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
namespace cartographer {
|
namespace cartographer {
|
||||||
|
@ -40,6 +42,8 @@ class MapBuilderTest : public ::testing::Test {
|
||||||
include "map_builder.lua"
|
include "map_builder.lua"
|
||||||
MAP_BUILDER.use_trajectory_builder_2d = true
|
MAP_BUILDER.use_trajectory_builder_2d = true
|
||||||
MAP_BUILDER.pose_graph.optimize_every_n_nodes = 0
|
MAP_BUILDER.pose_graph.optimize_every_n_nodes = 0
|
||||||
|
MAP_BUILDER.pose_graph.global_sampling_ratio = 0.05
|
||||||
|
MAP_BUILDER.pose_graph.global_constraint_search_after_n_seconds = 0
|
||||||
return MAP_BUILDER)text";
|
return MAP_BUILDER)text";
|
||||||
auto map_builder_parameters = test::ResolveLuaParameters(kMapBuilderLua);
|
auto map_builder_parameters = test::ResolveLuaParameters(kMapBuilderLua);
|
||||||
map_builder_options_ =
|
map_builder_options_ =
|
||||||
|
@ -85,6 +89,21 @@ class MapBuilderTest : public ::testing::Test {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int CreateTrajectoryWithFakeData() {
|
||||||
|
int trajectory_id = map_builder_->AddTrajectoryBuilder(
|
||||||
|
{kRangeSensorId}, trajectory_builder_options_,
|
||||||
|
GetLocalSlamResultCallback());
|
||||||
|
TrajectoryBuilderInterface* trajectory_builder =
|
||||||
|
map_builder_->GetTrajectoryBuilder(trajectory_id);
|
||||||
|
auto measurements = test::GenerateFakeRangeMeasurements(
|
||||||
|
kTravelDistance, kDuration, kTimeStep);
|
||||||
|
for (auto& measurement : measurements) {
|
||||||
|
trajectory_builder->AddSensorData(kRangeSensorId.id, measurement);
|
||||||
|
}
|
||||||
|
map_builder_->FinishTrajectory(trajectory_id);
|
||||||
|
return trajectory_id;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<MapBuilderInterface> map_builder_;
|
std::unique_ptr<MapBuilderInterface> map_builder_;
|
||||||
proto::MapBuilderOptions map_builder_options_;
|
proto::MapBuilderOptions map_builder_options_;
|
||||||
proto::TrajectoryBuilderOptions trajectory_builder_options_;
|
proto::TrajectoryBuilderOptions trajectory_builder_options_;
|
||||||
|
@ -189,6 +208,10 @@ TEST_F(MapBuilderTest, GlobalSlam2D) {
|
||||||
.norm(),
|
.norm(),
|
||||||
0.1 * kTravelDistance);
|
0.1 * kTravelDistance);
|
||||||
EXPECT_GE(map_builder_->pose_graph()->constraints().size(), 50);
|
EXPECT_GE(map_builder_->pose_graph()->constraints().size(), 50);
|
||||||
|
EXPECT_THAT(map_builder_->pose_graph()->constraints(),
|
||||||
|
testing::Contains(testing::Field(
|
||||||
|
&PoseGraphInterface::Constraint::tag,
|
||||||
|
PoseGraphInterface::Constraint::INTER_SUBMAP)));
|
||||||
const auto trajectory_nodes =
|
const auto trajectory_nodes =
|
||||||
map_builder_->pose_graph()->GetTrajectoryNodes();
|
map_builder_->pose_graph()->GetTrajectoryNodes();
|
||||||
EXPECT_GE(trajectory_nodes.SizeOfTrajectoryOrZero(trajectory_id), 20);
|
EXPECT_GE(trajectory_nodes.SizeOfTrajectoryOrZero(trajectory_id), 20);
|
||||||
|
@ -228,6 +251,10 @@ TEST_F(MapBuilderTest, GlobalSlam3D) {
|
||||||
.norm(),
|
.norm(),
|
||||||
0.1 * kTravelDistance);
|
0.1 * kTravelDistance);
|
||||||
EXPECT_GE(map_builder_->pose_graph()->constraints().size(), 10);
|
EXPECT_GE(map_builder_->pose_graph()->constraints().size(), 10);
|
||||||
|
EXPECT_THAT(map_builder_->pose_graph()->constraints(),
|
||||||
|
testing::Contains(testing::Field(
|
||||||
|
&PoseGraphInterface::Constraint::tag,
|
||||||
|
PoseGraphInterface::Constraint::INTER_SUBMAP)));
|
||||||
const auto trajectory_nodes =
|
const auto trajectory_nodes =
|
||||||
map_builder_->pose_graph()->GetTrajectoryNodes();
|
map_builder_->pose_graph()->GetTrajectoryNodes();
|
||||||
EXPECT_GE(trajectory_nodes.SizeOfTrajectoryOrZero(trajectory_id), 5);
|
EXPECT_GE(trajectory_nodes.SizeOfTrajectoryOrZero(trajectory_id), 5);
|
||||||
|
@ -240,6 +267,147 @@ TEST_F(MapBuilderTest, GlobalSlam3D) {
|
||||||
0.1 * kTravelDistance);
|
0.1 * kTravelDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(MapBuilderTest, SaveLoadState) {
|
||||||
|
for (int dimensions : {2, 3}) {
|
||||||
|
if (dimensions == 3) {
|
||||||
|
SetOptionsTo3D();
|
||||||
|
}
|
||||||
|
trajectory_builder_options_.mutable_trajectory_builder_2d_options()
|
||||||
|
->set_use_imu_data(true);
|
||||||
|
BuildMapBuilder();
|
||||||
|
int trajectory_id = map_builder_->AddTrajectoryBuilder(
|
||||||
|
{kRangeSensorId, kIMUSensorId}, trajectory_builder_options_,
|
||||||
|
GetLocalSlamResultCallback());
|
||||||
|
TrajectoryBuilderInterface* trajectory_builder =
|
||||||
|
map_builder_->GetTrajectoryBuilder(trajectory_id);
|
||||||
|
const auto measurements = test::GenerateFakeRangeMeasurements(
|
||||||
|
kTravelDistance, kDuration, kTimeStep);
|
||||||
|
for (const auto& measurement : measurements) {
|
||||||
|
trajectory_builder->AddSensorData(kRangeSensorId.id, measurement);
|
||||||
|
trajectory_builder->AddSensorData(
|
||||||
|
kIMUSensorId.id,
|
||||||
|
sensor::ImuData{measurement.time, Eigen::Vector3d(0., 0., 9.8),
|
||||||
|
Eigen::Vector3d::Zero()});
|
||||||
|
}
|
||||||
|
map_builder_->FinishTrajectory(trajectory_id);
|
||||||
|
map_builder_->pose_graph()->RunFinalOptimization();
|
||||||
|
int num_constraints = map_builder_->pose_graph()->constraints().size();
|
||||||
|
int num_nodes =
|
||||||
|
map_builder_->pose_graph()->GetTrajectoryNodes().SizeOfTrajectoryOrZero(
|
||||||
|
trajectory_id);
|
||||||
|
EXPECT_GT(num_constraints, 0);
|
||||||
|
EXPECT_GT(num_nodes, 0);
|
||||||
|
// TODO(gaschler): Consider using in-memory to avoid side effects.
|
||||||
|
const std::string filename = "temp-SaveLoadState.pbstream";
|
||||||
|
io::ProtoStreamWriter writer(filename);
|
||||||
|
map_builder_->SerializeState(&writer);
|
||||||
|
writer.Close();
|
||||||
|
|
||||||
|
// Reset 'map_builder_'.
|
||||||
|
BuildMapBuilder();
|
||||||
|
io::ProtoStreamReader reader(filename);
|
||||||
|
map_builder_->LoadState(&reader, false /* load_frozen_state */);
|
||||||
|
// TODO(gaschler): Design better way to find out which new trajectory_ids
|
||||||
|
// were created by LoadState.
|
||||||
|
map_builder_->pose_graph()->RunFinalOptimization();
|
||||||
|
auto nodes = map_builder_->pose_graph()->GetTrajectoryNodes();
|
||||||
|
ASSERT_GT(nodes.size(), 0);
|
||||||
|
int new_trajectory_id = nodes.begin()->id.trajectory_id;
|
||||||
|
EXPECT_EQ(num_constraints,
|
||||||
|
map_builder_->pose_graph()->constraints().size());
|
||||||
|
EXPECT_EQ(
|
||||||
|
num_nodes,
|
||||||
|
map_builder_->pose_graph()->GetTrajectoryNodes().SizeOfTrajectoryOrZero(
|
||||||
|
new_trajectory_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(MapBuilderTest, LocalizationOnFrozenTrajectory2D) {
|
||||||
|
BuildMapBuilder();
|
||||||
|
int temp_trajectory_id = CreateTrajectoryWithFakeData();
|
||||||
|
map_builder_->pose_graph()->RunFinalOptimization();
|
||||||
|
EXPECT_GT(map_builder_->pose_graph()->constraints().size(), 0);
|
||||||
|
EXPECT_GT(
|
||||||
|
map_builder_->pose_graph()->GetTrajectoryNodes().SizeOfTrajectoryOrZero(
|
||||||
|
temp_trajectory_id),
|
||||||
|
0);
|
||||||
|
const std::string filename = "temp-LocalizationOnFrozenTrajectory2D.pbstream";
|
||||||
|
io::ProtoStreamWriter writer(filename);
|
||||||
|
map_builder_->SerializeState(&writer);
|
||||||
|
writer.Close();
|
||||||
|
|
||||||
|
// Reset 'map_builder_'.
|
||||||
|
local_slam_result_poses_.clear();
|
||||||
|
SetOptionsEnableGlobalOptimization();
|
||||||
|
BuildMapBuilder();
|
||||||
|
io::ProtoStreamReader reader(filename);
|
||||||
|
map_builder_->LoadState(&reader, true /* load_frozen_state */);
|
||||||
|
map_builder_->pose_graph()->RunFinalOptimization();
|
||||||
|
int trajectory_id = map_builder_->AddTrajectoryBuilder(
|
||||||
|
{kRangeSensorId}, trajectory_builder_options_,
|
||||||
|
GetLocalSlamResultCallback());
|
||||||
|
TrajectoryBuilderInterface* trajectory_builder =
|
||||||
|
map_builder_->GetTrajectoryBuilder(trajectory_id);
|
||||||
|
transform::Rigid3d frozen_trajectory_to_global(
|
||||||
|
Eigen::Vector3d(0.5, 0.4, 0),
|
||||||
|
Eigen::Quaterniond(Eigen::AngleAxisd(1.2, Eigen::Vector3d::UnitZ())));
|
||||||
|
Eigen::Vector3d travel_translation =
|
||||||
|
Eigen::Vector3d(2., 1., 0.).normalized() * kTravelDistance;
|
||||||
|
auto measurements = test::GenerateFakeRangeMeasurements(
|
||||||
|
travel_translation.cast<float>(), kDuration, kTimeStep,
|
||||||
|
frozen_trajectory_to_global.cast<float>());
|
||||||
|
for (auto& measurement : measurements) {
|
||||||
|
measurement.time += common::FromSeconds(100.);
|
||||||
|
trajectory_builder->AddSensorData(kRangeSensorId.id, measurement);
|
||||||
|
}
|
||||||
|
map_builder_->FinishTrajectory(trajectory_id);
|
||||||
|
map_builder_->pose_graph()->RunFinalOptimization();
|
||||||
|
EXPECT_EQ(local_slam_result_poses_.size(), measurements.size());
|
||||||
|
EXPECT_NEAR(kTravelDistance,
|
||||||
|
(local_slam_result_poses_.back().translation() -
|
||||||
|
local_slam_result_poses_.front().translation())
|
||||||
|
.norm(),
|
||||||
|
0.15 * kTravelDistance);
|
||||||
|
EXPECT_GE(map_builder_->pose_graph()->constraints().size(), 50);
|
||||||
|
auto constraints = map_builder_->pose_graph()->constraints();
|
||||||
|
int num_cross_trajectory_constraints = 0;
|
||||||
|
for (const auto& constraint : constraints) {
|
||||||
|
if (constraint.node_id.trajectory_id !=
|
||||||
|
constraint.submap_id.trajectory_id) {
|
||||||
|
++num_cross_trajectory_constraints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EXPECT_GT(num_cross_trajectory_constraints, 3);
|
||||||
|
// TODO(gaschler): Subscribe global slam callback, verify that all nodes are
|
||||||
|
// optimized.
|
||||||
|
EXPECT_THAT(constraints, testing::Contains(testing::Field(
|
||||||
|
&PoseGraphInterface::Constraint::tag,
|
||||||
|
PoseGraphInterface::Constraint::INTER_SUBMAP)));
|
||||||
|
const auto trajectory_nodes =
|
||||||
|
map_builder_->pose_graph()->GetTrajectoryNodes();
|
||||||
|
EXPECT_GE(trajectory_nodes.SizeOfTrajectoryOrZero(trajectory_id), 20);
|
||||||
|
const auto submap_data = map_builder_->pose_graph()->GetAllSubmapData();
|
||||||
|
EXPECT_GE(submap_data.SizeOfTrajectoryOrZero(trajectory_id), 5);
|
||||||
|
const transform::Rigid3d global_pose =
|
||||||
|
map_builder_->pose_graph()->GetLocalToGlobalTransform(trajectory_id) *
|
||||||
|
local_slam_result_poses_.back();
|
||||||
|
EXPECT_NEAR(frozen_trajectory_to_global.translation().norm(),
|
||||||
|
map_builder_->pose_graph()
|
||||||
|
->GetLocalToGlobalTransform(trajectory_id)
|
||||||
|
.translation()
|
||||||
|
.norm(),
|
||||||
|
0.1);
|
||||||
|
const transform::Rigid3d expected_global_pose =
|
||||||
|
frozen_trajectory_to_global *
|
||||||
|
transform::Rigid3d::Translation(travel_translation);
|
||||||
|
EXPECT_NEAR(
|
||||||
|
0.,
|
||||||
|
(global_pose.translation() - expected_global_pose.translation()).norm(),
|
||||||
|
0.3)
|
||||||
|
<< "global_pose: " << global_pose
|
||||||
|
<< "expected_global_pose: " << expected_global_pose;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace mapping
|
} // namespace mapping
|
||||||
} // namespace cartographer
|
} // namespace cartographer
|
||||||
|
|
Loading…
Reference in New Issue