Make WriteAssets multi trajectory aware. (#394)

Related to #343.
master
Holger Rapp 2017-06-23 18:16:32 +02:00 committed by GitHub
parent 08ff3f9c42
commit 04df159d82
6 changed files with 122 additions and 101 deletions

View File

@ -31,23 +31,34 @@
namespace cartographer_ros { namespace cartographer_ros {
// Writes an occupancy grid. bool HasNonTrimmedNode(
const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
all_trajectory_nodes) {
for (const auto& trajectory_nodes : all_trajectory_nodes) {
for (const auto& node : trajectory_nodes) {
if (!node.trimmed()) {
return true;
}
}
}
return false;
}
void Write2DAssets( void Write2DAssets(
const std::vector<::cartographer::mapping::TrajectoryNode>& const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
trajectory_nodes, all_trajectory_nodes,
const string& map_frame, const string& map_frame,
const ::cartographer::mapping_2d::proto::SubmapsOptions& submaps_options, const ::cartographer::mapping_2d::proto::SubmapsOptions& submaps_options,
const std::string& stem) { const std::string& stem) {
::nav_msgs::OccupancyGrid occupancy_grid; ::nav_msgs::OccupancyGrid occupancy_grid;
BuildOccupancyGrid2D(trajectory_nodes, map_frame, submaps_options, BuildOccupancyGrid2D(all_trajectory_nodes, map_frame, submaps_options,
&occupancy_grid); &occupancy_grid);
WriteOccupancyGridToPgmAndYaml(occupancy_grid, stem); WriteOccupancyGridToPgmAndYaml(occupancy_grid, stem);
} }
// Writes X-ray images and PLY files from the 'trajectory_nodes'. The filenames void Write3DAssets(
// will all start with 'stem'. const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
void Write3DAssets(const std::vector<::cartographer::mapping::TrajectoryNode>& all_trajectory_nodes,
trajectory_nodes,
const double voxel_size, const std::string& stem) { const double voxel_size, const std::string& stem) {
namespace carto = ::cartographer; namespace carto = ::cartographer;
const auto file_writer_factory = [](const string& filename) { const auto file_writer_factory = [](const string& filename) {
@ -73,17 +84,21 @@ void Write3DAssets(const std::vector<::cartographer::mapping::TrajectoryNode>&
carto::io::PlyWritingPointsProcessor ply_writing_points_processor( carto::io::PlyWritingPointsProcessor ply_writing_points_processor(
file_writer_factory(stem + ".ply"), &xz_xray_points_processor); file_writer_factory(stem + ".ply"), &xz_xray_points_processor);
for (const auto& node : trajectory_nodes) { for (size_t trajectory_id = 0; trajectory_id < all_trajectory_nodes.size();
++trajectory_id) {
for (const auto& node : all_trajectory_nodes[trajectory_id]) {
const carto::sensor::RangeData range_data = const carto::sensor::RangeData range_data =
carto::sensor::TransformRangeData( carto::sensor::TransformRangeData(
carto::sensor::Decompress(node.constant_data->range_data_3d), carto::sensor::Decompress(node.constant_data->range_data_3d),
node.pose.cast<float>()); node.pose.cast<float>());
auto points_batch = carto::common::make_unique<carto::io::PointsBatch>(); auto points_batch = carto::common::make_unique<carto::io::PointsBatch>();
points_batch->time = node.time();
points_batch->origin = range_data.origin; points_batch->origin = range_data.origin;
points_batch->trajectory_index = trajectory_id;
points_batch->points = range_data.returns; points_batch->points = range_data.returns;
ply_writing_points_processor.Process(std::move(points_batch)); ply_writing_points_processor.Process(std::move(points_batch));
} }
}
ply_writing_points_processor.Flush(); ply_writing_points_processor.Flush();
} }

View File

@ -25,18 +25,25 @@
namespace cartographer_ros { namespace cartographer_ros {
// Returns 'true' if there is at least one untrimmed node for any trajectory.
// The Write?DAssets functions expects this to be 'true'.
bool HasNonTrimmedNode(
const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
all_trajectory_nodes);
// Writes a trajectory proto and an occupancy grid. // Writes a trajectory proto and an occupancy grid.
void Write2DAssets( void Write2DAssets(
const std::vector<::cartographer::mapping::TrajectoryNode>& const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
trajectory_nodes, all_trajectory_nodes,
const string& map_frame, const string& map_frame,
const ::cartographer::mapping_2d::proto::SubmapsOptions& submaps_options, const ::cartographer::mapping_2d::proto::SubmapsOptions& submaps_options,
const std::string& stem); const std::string& stem);
// Writes X-ray images, trajectory proto, and PLY files from the // Writes X-ray images, trajectory proto, and PLY files from the
// 'trajectory_nodes'. The filenames will all start with 'stem'. // 'all_trajectory_nodes'. The filenames will all start with 'stem'.
void Write3DAssets(const std::vector<::cartographer::mapping::TrajectoryNode>& void Write3DAssets(
trajectory_nodes, const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
all_trajectory_nodes,
const double voxel_size, const std::string& stem); const double voxel_size, const std::string& stem);
} // namespace cartographer_ros } // namespace cartographer_ros

View File

@ -38,6 +38,7 @@ int MapBuilderBridge::AddTrajectory(
expected_sensor_ids, trajectory_options.trajectory_builder_options); expected_sensor_ids, trajectory_options.trajectory_builder_options);
LOG(INFO) << "Added trajectory with ID '" << trajectory_id << "'."; LOG(INFO) << "Added trajectory with ID '" << trajectory_id << "'.";
// Make sure there is no trajectory with 'trajectory_id' yet.
CHECK_EQ(sensor_bridges_.count(trajectory_id), 0); CHECK_EQ(sensor_bridges_.count(trajectory_id), 0);
sensor_bridges_[trajectory_id] = sensor_bridges_[trajectory_id] =
cartographer::common::make_unique<SensorBridge>( cartographer::common::make_unique<SensorBridge>(
@ -53,6 +54,7 @@ int MapBuilderBridge::AddTrajectory(
void MapBuilderBridge::FinishTrajectory(const int trajectory_id) { void MapBuilderBridge::FinishTrajectory(const int trajectory_id) {
LOG(INFO) << "Finishing trajectory with ID '" << trajectory_id << "'..."; LOG(INFO) << "Finishing trajectory with ID '" << trajectory_id << "'...";
// Make sure there is a trajectory with 'trajectory_id'.
CHECK_EQ(sensor_bridges_.count(trajectory_id), 1); CHECK_EQ(sensor_bridges_.count(trajectory_id), 1);
map_builder_.FinishTrajectory(trajectory_id); map_builder_.FinishTrajectory(trajectory_id);
map_builder_.sparse_pose_graph()->RunFinalOptimization(); map_builder_.sparse_pose_graph()->RunFinalOptimization();
@ -72,16 +74,18 @@ void MapBuilderBridge::SerializeState(const std::string& stem) {
void MapBuilderBridge::WriteAssets(const string& stem) { void MapBuilderBridge::WriteAssets(const string& stem) {
const auto all_trajectory_nodes = const auto all_trajectory_nodes =
map_builder_.sparse_pose_graph()->GetTrajectoryNodes(); map_builder_.sparse_pose_graph()->GetTrajectoryNodes();
// TODO(yutakaoka): Add multi-trajectory support. if (!HasNonTrimmedNode(all_trajectory_nodes)) {
CHECK_EQ(trajectory_options_.count(0), 1);
CHECK_EQ(all_trajectory_nodes.size(), 1);
if (all_trajectory_nodes[0].empty()) {
LOG(WARNING) << "No data was collected and no assets will be written."; LOG(WARNING) << "No data was collected and no assets will be written.";
} else { return;
}
// Make sure there is a trajectory with id = 0.
CHECK_EQ(trajectory_options_.count(0), 1);
LOG(INFO) << "Writing assets with stem '" << stem << "'..."; LOG(INFO) << "Writing assets with stem '" << stem << "'...";
if (node_options_.map_builder_options.use_trajectory_builder_2d()) { if (node_options_.map_builder_options.use_trajectory_builder_2d()) {
// We arbitrarily use the submap_options() from the first trajectory to
// write the 2D assets.
Write2DAssets( Write2DAssets(
all_trajectory_nodes[0], node_options_.map_frame, all_trajectory_nodes, node_options_.map_frame,
trajectory_options_[0] trajectory_options_[0]
.trajectory_builder_options.trajectory_builder_2d_options() .trajectory_builder_options.trajectory_builder_2d_options()
.submaps_options(), .submaps_options(),
@ -90,14 +94,13 @@ void MapBuilderBridge::WriteAssets(const string& stem) {
if (node_options_.map_builder_options.use_trajectory_builder_3d()) { if (node_options_.map_builder_options.use_trajectory_builder_3d()) {
Write3DAssets( Write3DAssets(
all_trajectory_nodes[0], all_trajectory_nodes,
trajectory_options_[0] trajectory_options_[0]
.trajectory_builder_options.trajectory_builder_3d_options() .trajectory_builder_options.trajectory_builder_3d_options()
.submaps_options() .submaps_options()
.high_resolution(), .high_resolution(),
stem); stem);
} }
}
} }
bool MapBuilderBridge::HandleSubmapQuery( bool MapBuilderBridge::HandleSubmapQuery(
@ -153,20 +156,16 @@ std::unique_ptr<nav_msgs::OccupancyGrid>
MapBuilderBridge::BuildOccupancyGrid() { MapBuilderBridge::BuildOccupancyGrid() {
CHECK(node_options_.map_builder_options.use_trajectory_builder_2d()) CHECK(node_options_.map_builder_options.use_trajectory_builder_2d())
<< "Publishing OccupancyGrids for 3D data is not yet supported"; << "Publishing OccupancyGrids for 3D data is not yet supported";
std::vector<::cartographer::mapping::TrajectoryNode> flat_trajectory_nodes; const auto all_trajectory_nodes =
for (const auto& single_trajectory_nodes : map_builder_.sparse_pose_graph()->GetTrajectoryNodes();
map_builder_.sparse_pose_graph()->GetTrajectoryNodes()) {
flat_trajectory_nodes.insert(flat_trajectory_nodes.end(),
single_trajectory_nodes.begin(),
single_trajectory_nodes.end());
}
std::unique_ptr<nav_msgs::OccupancyGrid> occupancy_grid; std::unique_ptr<nav_msgs::OccupancyGrid> occupancy_grid;
if (!flat_trajectory_nodes.empty()) { if (HasNonTrimmedNode(all_trajectory_nodes)) {
occupancy_grid = occupancy_grid =
cartographer::common::make_unique<nav_msgs::OccupancyGrid>(); cartographer::common::make_unique<nav_msgs::OccupancyGrid>();
// Make sure there is a trajectory with id = 0.
CHECK_EQ(trajectory_options_.count(0), 1); CHECK_EQ(trajectory_options_.count(0), 1);
BuildOccupancyGrid2D( BuildOccupancyGrid2D(
flat_trajectory_nodes, node_options_.map_frame, all_trajectory_nodes, node_options_.map_frame,
trajectory_options_[0] trajectory_options_[0]
.trajectory_builder_options.trajectory_builder_2d_options() .trajectory_builder_options.trajectory_builder_2d_options()
.submaps_options(), .submaps_options(),
@ -190,6 +189,7 @@ MapBuilderBridge::GetTrajectoryStates() {
continue; continue;
} }
// Make sure there is a trajectory with 'trajectory_id'.
CHECK_EQ(trajectory_options_.count(trajectory_id), 1); CHECK_EQ(trajectory_options_.count(trajectory_id), 1);
trajectory_states[trajectory_id] = { trajectory_states[trajectory_id] = {
pose_estimate, pose_estimate,

View File

@ -24,24 +24,19 @@
namespace { namespace {
Eigen::AlignedBox2f ComputeMapBoundingBox( Eigen::AlignedBox2f ComputeMapBoundingBox2D(
const std::vector<::cartographer::mapping::TrajectoryNode>& const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
trajectory_nodes) { all_trajectory_nodes) {
Eigen::AlignedBox2f bounding_box(Eigen::Vector2f::Zero()); Eigen::AlignedBox2f bounding_box(Eigen::Vector2f::Zero());
for (const auto& trajectory_nodes : all_trajectory_nodes) {
for (const auto& node : trajectory_nodes) { for (const auto& node : trajectory_nodes) {
if (node.trimmed()) { if (node.trimmed()) {
continue; continue;
} }
const auto& data = *node.constant_data; const auto& data = *node.constant_data;
::cartographer::sensor::RangeData range_data; ::cartographer::sensor::RangeData range_data;
if (!data.range_data_3d.returns.empty()) {
range_data = ::cartographer::sensor::TransformRangeData(
::cartographer::sensor::Decompress(data.range_data_3d),
node.pose.cast<float>());
} else {
range_data = ::cartographer::sensor::TransformRangeData( range_data = ::cartographer::sensor::TransformRangeData(
data.range_data_2d, node.pose.cast<float>()); data.range_data_2d, node.pose.cast<float>());
}
bounding_box.extend(range_data.origin.head<2>()); bounding_box.extend(range_data.origin.head<2>());
for (const Eigen::Vector3f& hit : range_data.returns) { for (const Eigen::Vector3f& hit : range_data.returns) {
bounding_box.extend(hit.head<2>()); bounding_box.extend(hit.head<2>());
@ -50,6 +45,7 @@ Eigen::AlignedBox2f ComputeMapBoundingBox(
bounding_box.extend(miss.head<2>()); bounding_box.extend(miss.head<2>());
} }
} }
}
return bounding_box; return bounding_box;
} }
@ -58,31 +54,33 @@ Eigen::AlignedBox2f ComputeMapBoundingBox(
namespace cartographer_ros { namespace cartographer_ros {
void BuildOccupancyGrid2D( void BuildOccupancyGrid2D(
const std::vector<::cartographer::mapping::TrajectoryNode>& const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
trajectory_nodes, all_trajectory_nodes,
const string& map_frame, const string& map_frame,
const ::cartographer::mapping_2d::proto::SubmapsOptions& submaps_options, const ::cartographer::mapping_2d::proto::SubmapsOptions& submaps_options,
::nav_msgs::OccupancyGrid* const occupancy_grid) { ::nav_msgs::OccupancyGrid* const occupancy_grid) {
CHECK(!trajectory_nodes.empty());
namespace carto = ::cartographer; namespace carto = ::cartographer;
const carto::mapping_2d::MapLimits map_limits = const carto::mapping_2d::MapLimits map_limits =
ComputeMapLimits(submaps_options.resolution(), trajectory_nodes); ComputeMapLimits(submaps_options.resolution(), all_trajectory_nodes);
carto::mapping_2d::ProbabilityGrid probability_grid(map_limits); carto::mapping_2d::ProbabilityGrid probability_grid(map_limits);
carto::mapping_2d::RangeDataInserter range_data_inserter( carto::mapping_2d::RangeDataInserter range_data_inserter(
submaps_options.range_data_inserter_options()); submaps_options.range_data_inserter_options());
carto::common::Time latest_time = carto::common::Time::min();
for (const auto& trajectory_nodes : all_trajectory_nodes) {
for (const auto& node : trajectory_nodes) { for (const auto& node : trajectory_nodes) {
if (node.trimmed()) { if (node.trimmed()) {
continue; continue;
} }
CHECK(node.constant_data->range_data_3d.returns.empty()); // No 3D yet. latest_time = std::max(latest_time, node.time());
CHECK(node.constant_data->range_data_3d.returns.empty());
range_data_inserter.Insert( range_data_inserter.Insert(
carto::sensor::TransformRangeData(node.constant_data->range_data_2d, carto::sensor::TransformRangeData(node.constant_data->range_data_2d,
node.pose.cast<float>()), node.pose.cast<float>()),
&probability_grid); &probability_grid);
} }
}
// TODO(whess): Compute the latest time of in 'trajectory_nodes'. CHECK(latest_time != carto::common::Time::min());
occupancy_grid->header.stamp = ToRos(trajectory_nodes.back().time()); occupancy_grid->header.stamp = ToRos(latest_time);
occupancy_grid->header.frame_id = map_frame; occupancy_grid->header.frame_id = map_frame;
occupancy_grid->info.map_load_time = occupancy_grid->header.stamp; occupancy_grid->info.map_load_time = occupancy_grid->header.stamp;
@ -128,9 +126,10 @@ void BuildOccupancyGrid2D(
::cartographer::mapping_2d::MapLimits ComputeMapLimits( ::cartographer::mapping_2d::MapLimits ComputeMapLimits(
const double resolution, const double resolution,
const std::vector<::cartographer::mapping::TrajectoryNode>& const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
trajectory_nodes) { all_trajectory_nodes) {
Eigen::AlignedBox2f bounding_box = ComputeMapBoundingBox(trajectory_nodes); Eigen::AlignedBox2f bounding_box =
ComputeMapBoundingBox2D(all_trajectory_nodes);
// Add some padding to ensure all rays are still contained in the map after // Add some padding to ensure all rays are still contained in the map after
// discretization. // discretization.
const float kPadding = 3.f * resolution; const float kPadding = 3.f * resolution;

View File

@ -29,8 +29,8 @@
namespace cartographer_ros { namespace cartographer_ros {
void BuildOccupancyGrid2D( void BuildOccupancyGrid2D(
const std::vector<::cartographer::mapping::TrajectoryNode>& const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
trajectory_nodes, all_trajectory_nodes,
const string& map_frame, const string& map_frame,
const ::cartographer::mapping_2d::proto::SubmapsOptions& submaps_options, const ::cartographer::mapping_2d::proto::SubmapsOptions& submaps_options,
::nav_msgs::OccupancyGrid* const occupancy_grid); ::nav_msgs::OccupancyGrid* const occupancy_grid);
@ -39,8 +39,8 @@ void BuildOccupancyGrid2D(
// misses) in the 'trajectory_nodes'. // misses) in the 'trajectory_nodes'.
::cartographer::mapping_2d::MapLimits ComputeMapLimits( ::cartographer::mapping_2d::MapLimits ComputeMapLimits(
double resolution, double resolution,
const std::vector<::cartographer::mapping::TrajectoryNode>& const std::vector<std::vector<::cartographer::mapping::TrajectoryNode>>&
trajectory_nodes); all_trajectory_nodes);
} // namespace cartographer_ros } // namespace cartographer_ros

View File

@ -41,7 +41,7 @@ TEST(OccupancyGridTest, ComputeMapLimits) {
::cartographer::transform::Rigid3d::Identity()}; ::cartographer::transform::Rigid3d::Identity()};
constexpr double kResolution = 0.05; constexpr double kResolution = 0.05;
const ::cartographer::mapping_2d::MapLimits limits = const ::cartographer::mapping_2d::MapLimits limits =
ComputeMapLimits(kResolution, {trajectory_node}); ComputeMapLimits(kResolution, {{trajectory_node}});
constexpr float kPaddingAwareTolerance = 5 * kResolution; constexpr float kPaddingAwareTolerance = 5 * kResolution;
EXPECT_NEAR(50.f, limits.max().x(), kPaddingAwareTolerance); EXPECT_NEAR(50.f, limits.max().x(), kPaddingAwareTolerance);
EXPECT_NEAR(1.f, limits.max().y(), kPaddingAwareTolerance); EXPECT_NEAR(1.f, limits.max().y(), kPaddingAwareTolerance);