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

Check for collisions in motion primitives #889

Merged

Conversation

bpwilcox
Copy link

@bpwilcox bpwilcox commented Jun 25, 2019

This PR requires #737.

Basic Info

Info Please fill out this column
Ticket(s) this addresses #533
Primary OS tested on Ubuntu 18.04
Robotic platform tested on Turtlebot3

Description of contribution in a few bullet points

Future work that may be required in bullet points

  • Add robust spin and backup tests

@bpwilcox bpwilcox force-pushed the motion_primitives_collision_checking branch 2 times, most recently from 53d6c02 to 8ab347d Compare June 26, 2019 22:23
@bpwilcox
Copy link
Author

Changing the robot radius default to match the turtlebot3 documentation significantly improved the performance both during normal operation and using motion primitives. The footprint is centered off the base link, which is seemingly the between the wheels on the front, so it is not symmetrical around the robot's center. We may want to define the actual pointwise footprint later.

@bpwilcox bpwilcox changed the title [WIP] Check for collisions in motion primitives Check for collisions in motion primitives Jun 26, 2019
@crdelsey crdelsey added this to the D Turtle Release milestone Jun 27, 2019
@orduno
Copy link
Contributor

orduno commented Jun 27, 2019

The footprint is centered off the base link, which is seemingly the between the wheels on the front, so it is not symmetrical around the robot's center. We may want to define the actual pointwise footprint later.

One alternative would be to define a static transform between the base link and the robot center. Seems like the costmaps should reference the center of the robot rather than the location of the drive system. We can open an issue and address this later.


~CollisionChecker();

double scorePose(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the line break for these three methods?

@@ -77,11 +78,13 @@ class Costmap2DPublisher
{
costmap_pub_->on_activate();
costmap_update_pub_->on_activate();
costmap_raw_pub_->on_activate();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the code, I'm not clear yet on what costmap_raw_pub_ is. A comment would be helpful. I suppose there is some kind of raw costmap that is being published?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to change the name of all the pubs actually. The costmap_raw_pub publishes the costmap using the native values from the costmap2D array. The original costmap_pub' is publishing a translated occupancy grid version for rviz visualization. Would it perhaps be more helpful to change the costmap_pubtocostmap_rviz_pub`?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I wouldn't specify who I expect to be listening. Just keep it to a topic name that anyone can listen to.

nav2_util::LifecycleNode::SharedPtr node,
std::string & topic_name);

CostmapSubscriber(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Glad you included this version of the constructor. I'm thinking we should standardize on provided all three kinds of constructors: rclcpp::Node, rclcpp_lifecycle::LifecycleNode, and the interfaces version. The first two end up just using the third and are provided for convenience.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I'll add the rclcpp::Node version. Also, in the class I'm only currently using say one of the node interfaces, should we always stick to pattern even if we never use the other interfaces?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we expect it to be a class that might have multiple clients, then I would include them. If it's done specifically for just one module implementation, you can probably skip it.

// See the License for the specific language governing permissions and
// limitations under the License.

#include "nav2_costmap_2d/collision_checker.hpp"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI - The convention I've been using for implementation (.cpp) files:

Include the file's header first. In this case collision_checker.hpp. Next, another section to include any standard header files. Finally, the last section includes any headers that are required by the implementation. Each of the three sections are sorted.

So, in this case, it would look like:

#include "nav2_costmap_2d/collision_checker.hpp"

#include "nav2_costmap_2d/cost_values.hpp"
#include "nav2_costmap_2d/exceptions.hpp"
#include "nav2_costmap_2d/footprint.hpp"
#include "nav2_util/line_iterator.hpp"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use that convention too, this was an oversight, thanks for catching!

collision_checker_ = std::make_unique<nav2_costmap_2d::CollisionChecker>(
costmap_sub_, footprint_sub_, get_name());

get_robot_pose_service_ = rclcpp_node_->create_service<nav2_msgs::srv::GetRobotPose>(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should be able to create the service off of the main lifecycle node and therefore not require the rclcpp_node.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had tried to do that before, but it did not work since the service was hanging because I'm not actually spinning the lifecycle node here in this test. I used the rclcpp_node because it is spinning in a thread.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, np.

nav2_msgs::msg::Costmap
toCostmapMsg(nav2_costmap_2d::Costmap2D * costmap)
{
double resolution = costmap->getResolution();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A personal, stylistic thing, but I'd organize this code to get the various values first, and then populate the structure. For example,

    double resolution = costmap->getResolution();

    double wx, wy;
    costmap->mapToWorld(0, 0, wx, wy);

    unsigned char * data = costmap->getCharMap();

    nav2_msgs::msg::Costmap costmap_msg;
    costmap_msg.header.frame_id = global_frame_;
    costmap_msg.header.stamp = now();
    costmap_msg.metadata.layer = "master";
    costmap_msg.metadata.resolution = resolution;
    costmap_msg.metadata.size_x = costmap->getSizeInCellsX();
    costmap_msg.metadata.size_y = costmap->getSizeInCellsY();
    costmap_msg.metadata.origin.position.x = wx - resolution / 2;
    costmap_msg.metadata.origin.position.y = wy - resolution / 2;
    costmap_msg.metadata.origin.position.z = 0.0;
    costmap_msg.metadata.origin.orientation.w = 1.0;
    costmap_msg.data.resize(costmap_msg.metadata.size_x * costmap_msg.metadata.size_y);

    for (unsigned int i = 0; i < costmap_msg.data.size(); i++) {
      costmap_msg.data[i] = data[i];
    }

     return costmap_msg;

I think the result is a bit easier to read and to see all of the structure fields.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I like that.

if (!collision_checker_->isCollisionFree(pose2d)) {
stopRobot();
RCLCPP_INFO(node_->get_logger(), "Collision Ahead -Exiting Spin ");
return Status::SUCCEEDED;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should is case be considered successful? We weren't able to fully execute the spin.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about this, so I welcome feedback. If we return failure, then I noticed that the recovery behaviors exit and the system returns navigation failed. If we have another recovery behavior queued after spin, then we'd want to attempt to execute it even if we could not fully execute the spin. Also, I don't think that being unable to execute the full spin because of an impending collision is considered a "failure". If we can only turn 90 degrees out of the full 180 because a block prevents the rest, I'd still consider that recovery a success so far as it was implemented without any system failures or missing any data dependencies. If we DID consider it a failure, then I would still want that we don't break out of the recovery tree.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should return FAILED, the action server will set the goal as aborted in that case.

Copy link
Author

@bpwilcox bpwilcox Jun 27, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should abort the goal. Even if we consider this one recovery behavior "FAILED", I don't think we should necessarily abort the goal, since the next recovery could be successful. But firstly, I would want to hear why you think we should consider it a failure if the spin doesn't complete because of an impending obstacle. To me, that's sensible behavior that you would spin as much as you can before finishing and moving onto next recovery if needed, not that the spin itself failed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok with me to return FAILED for now, but we should address this ASAP.

Copy link
Contributor

@orduno orduno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review in-progress...

#include "geometry_msgs/msg/pose_stamped.hpp"
#include "geometry_msgs/msg/pose2_d.hpp"
#include "nav2_costmap_2d/costmap_2d.hpp"
#include "nav2_costmap_2d/costmap_subscriber.hpp"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor recommendation, some of these includes could be replaced with forward declarations:

class CostmapSubscriber;
class FootprintSubscriber;
class Costmap2D;

const geometry_msgs::msg::Pose2D & pose);
bool isCollisionFree(
const geometry_msgs::msg::Pose2D & pose);
bool getRobotPose(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if getRobotPose() is something we want from the CollisionChecker, maybe better to make it private.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it public so that I can call it in the motion primitives as opposed to creating a new service and function for the motion primitives. My assumption was that since the motion primitives base class creates a collision_checker, it would be redundant to duplicate this functionality. What do you think?


std::string name_;
nav2_util::GetRobotPoseClient get_robot_pose_client_{"collision_checker"};
std::shared_ptr<CostmapSubscriber> costmap_sub_;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider references instead of pointers.

}

bool
CollisionChecker::getRobotPose(geometry_msgs::msg::Pose & current_pose)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method can be const

auto request = std::make_shared<nav2_util::GetRobotPoseClient::GetRobotPoseRequest>();

auto result = get_robot_pose_client_.invoke(request, 1s);
if (!result.get()->is_pose_valid) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!result->is_pose_valid should also work.

if (!result.get()->is_pose_valid) {
return false;
}
current_pose = result.get()->pose.pose;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to get the pointed object.

return line_cost;
}

double CollisionChecker::pointCost(int x, int y)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method can be const

return footprint_cost;
}

double CollisionChecker::lineCost(int x0, int x1, int y0, int y1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method can be const


// now we really have to lay down the footprint in the costmap_ grid
unsigned int x0, x1, y0, y1;
double line_cost = 0.0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line_cost could be declared inside the for loop

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we want to do that here?

Copy link
Contributor

@orduno orduno left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good. Just some minor recommendations.

}

line_cost = lineCost(x0, x1, y0, y1);
footprint_cost = std::max(line_cost, footprint_cost);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

max_cost = std::max(lineCost(...), max_cost);

// we need to rasterize each line in the footprint
for (unsigned int i = 0; i < footprint.size() - 1; ++i) {
// get the cell coord of the first point
if (!costmap_->worldToMap(footprint[i].x, footprint[i].y, x0, y0)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make this a method.

@@ -59,6 +59,8 @@ Costmap2DPublisher::Costmap2DPublisher(
// TODO(bpwilcox): port onNewSubscription functionality for publisher
costmap_pub_ = ros_node->create_publisher<nav_msgs::msg::OccupancyGrid>(topic_name,
custom_qos);
costmap_raw_pub_ = ros_node->create_publisher<nav2_msgs::msg::Costmap>(topic_name + "_raw",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use node_ instead

rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_;

void toCostmap2D();
void costmap_callback(const nav2_msgs::msg::Costmap::SharedPtr msg);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

costmapCallback

public:
explicit CollisionCheckerException(const std::string description)
: std::runtime_error(description) {}
typedef std::shared_ptr<CollisionCheckerException> Ptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typedef not used here

@@ -45,8 +45,8 @@ Status BackUp::onRun(const std::shared_ptr<const BackUpAction::Goal> command)

command_x_ = command->target.x;

if (!robot_->getOdometry(initial_pose_)) {
RCLCPP_ERROR(node_->get_logger(), "initial robot odom pose is not available.");
if (!collision_checker_->getRobotPose(initial_pose_)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively use the GetRobotPoseClient.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we duplicate the GetRobotPoseClient here and in the collision_checker?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on our conversation, can we reference an issue # so we can track it later?


if (!collision_checker_->isCollisionFree(pose2d)) {
stopRobot();
RCLCPP_INFO(node_->get_logger(), "Collision Ahead - Exiting BackUp");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this RCLCPP_WARN

if (!collision_checker_->isCollisionFree(pose2d)) {
stopRobot();
RCLCPP_INFO(node_->get_logger(), "Collision Ahead - Exiting BackUp");
return Status::SUCCEEDED;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return FAILED

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, let's return SUCCEEDED for now, but open an issue to change it soon.

if (!collision_checker_->isCollisionFree(pose2d)) {
stopRobot();
RCLCPP_INFO(node_->get_logger(), "Collision Ahead -Exiting Spin ");
return Status::SUCCEEDED;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should return FAILED, the action server will set the goal as aborted in that case.

@bpwilcox bpwilcox force-pushed the motion_primitives_collision_checking branch from 615776e to da4f12c Compare June 28, 2019 22:05
@@ -77,11 +78,13 @@ class Costmap2DPublisher
{
costmap_pub_->on_activate();
costmap_update_pub_->on_activate();
costmap_raw_pub_->on_activate();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I wouldn't specify who I expect to be listening. Just keep it to a topic name that anyone can listen to.

nav2_util::LifecycleNode::SharedPtr node,
std::string & topic_name);

CostmapSubscriber(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we expect it to be a class that might have multiple clients, then I would include them. If it's done specifically for just one module implementation, you can probably skip it.

collision_checker_ = std::make_unique<nav2_costmap_2d::CollisionChecker>(
costmap_sub_, footprint_sub_, get_name());

get_robot_pose_service_ = rclcpp_node_->create_service<nav2_msgs::srv::GetRobotPose>(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, np.

if (!collision_checker_->isCollisionFree(pose2d)) {
stopRobot();
RCLCPP_INFO(node_->get_logger(), "Collision Ahead -Exiting Spin ");
return Status::SUCCEEDED;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok with me to return FAILED for now, but we should address this ASAP.

@bpwilcox bpwilcox merged commit 328983d into ros-navigation:master Jun 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants