-
Notifications
You must be signed in to change notification settings - Fork 123
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
unicycle motion model: add option to rotate the process noise covariance to the current state orientation #325
base: devel
Are you sure you want to change the base?
Changes from 6 commits
66cd212
7238f0d
05abcac
555ac04
04592ef
7f8b97a
ecb11a0
39bbb99
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,6 +123,26 @@ class Unicycle2D : public fuse_core::AsyncMotionModel | |
*/ | ||
bool applyCallback(fuse_core::Transaction& transaction) override; | ||
|
||
/** | ||
* @brief Generator function for providing to the \c TimestampManager to create a single motion model segment | ||
* between the specified timestamps. | ||
* | ||
* This function is effectively using \c generateMotionModel. | ||
* | ||
* @param[in] beginning_stamp The beginning timestamp of the motion model constraints to be generated. | ||
* \p beginning_stamp is guaranteed to be less than \p ending_stamp. | ||
* @param[in] ending_stamp The ending timestamp of the motion model constraints to be generated. | ||
* \p ending_stamp is guaranteed to be greater than \p beginning_stamp. | ||
* @param[out] constraints One or more motion model constraints between the requested timestamps. | ||
* @param[out] variables One or more variables at both the \p beginning_stamp and \p ending_stamp. The | ||
* variables should include initial values for the optimizer. | ||
*/ | ||
void generateMotionModelGenerator( | ||
const ros::Time& beginning_stamp, | ||
const ros::Time& ending_stamp, | ||
std::vector<fuse_core::Constraint::SharedPtr>& constraints, | ||
std::vector<fuse_core::Variable::SharedPtr>& variables); | ||
|
||
/** | ||
* @brief Generate a single motion model segment between the specified timestamps. | ||
* | ||
|
@@ -133,13 +153,30 @@ class Unicycle2D : public fuse_core::AsyncMotionModel | |
* \p beginning_stamp is guaranteed to be less than \p ending_stamp. | ||
* @param[in] ending_stamp The ending timestamp of the motion model constraints to be generated. | ||
* \p ending_stamp is guaranteed to be greater than \p beginning_stamp. | ||
* @param[in] state_history The state history object to be updated | ||
* @param[in] device_id The UUID of the device to be published | ||
* @param[in] name The unique name for this motion model instance | ||
* @param[in] process_noise_covariance Process noise covariance matrix | ||
* @param[in] scale_process_noise Whether to scale the process noise covariance pose by the norm | ||
of the current state twist. | ||
* @param[in] velocity_norm_min The minimum velocity/twist norm allowed when scaling the | ||
process noise covariance | ||
* @param[in] disable_checks Whether to disable the validation checks for the current and predicted state, | ||
including the process noise covariance after it is scaled and multiplied by dt. | ||
* @param[out] constraints One or more motion model constraints between the requested timestamps. | ||
* @param[out] variables One or more variables at both the \p beginning_stamp and \p ending_stamp. The | ||
* variables should include initial values for the optimizer. | ||
*/ | ||
void generateMotionModel( | ||
static void generateMotionModel( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this breaks existing code because it might break the API for children classes. 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you have to make this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I made this function static because I am honestly not that familiar with gtest and setting the private variables |
||
const ros::Time& beginning_stamp, | ||
const ros::Time& ending_stamp, | ||
StateHistory& state_history, | ||
const fuse_core::UUID& device_id, | ||
const std::string name, | ||
fuse_core::Matrix8d process_noise_covariance, | ||
const bool scale_process_noise, | ||
const double velocity_norm_min, | ||
const bool disable_checks, | ||
std::vector<fuse_core::Constraint::SharedPtr>& constraints, | ||
std::vector<fuse_core::Variable::SharedPtr>& variables); | ||
|
||
|
@@ -178,7 +215,7 @@ class Unicycle2D : public fuse_core::AsyncMotionModel | |
* | ||
* @param[in] state1 The first/oldest state | ||
* @param[in] state2 The second/newest state | ||
* @param[in] process_noise_covariance The process noise covariance, after it is scaled and multiplied by dt | ||
* @param[in] process_noise_covariance The process noise covariance, after it is rotated, scaled and multiplied by dt | ||
*/ | ||
static void validateMotionModel(const StateHistoryElement& state1, const StateHistoryElement& state2, | ||
const fuse_core::Matrix8d& process_noise_covariance); | ||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -119,7 +119,7 @@ Unicycle2D::Unicycle2D() : | |||||||||
fuse_core::AsyncMotionModel(1), | ||||||||||
buffer_length_(ros::DURATION_MAX), | ||||||||||
device_id_(fuse_core::uuid::NIL), | ||||||||||
timestamp_manager_(&Unicycle2D::generateMotionModel, this, ros::DURATION_MAX) | ||||||||||
timestamp_manager_(&Unicycle2D::generateMotionModelGenerator, this, ros::DURATION_MAX) | ||||||||||
{ | ||||||||||
} | ||||||||||
|
||||||||||
|
@@ -204,6 +204,7 @@ void Unicycle2D::onInit() | |||||||||
process_noise_covariance_ = fuse_core::Vector8d(process_noise_diagonal.data()).asDiagonal(); | ||||||||||
|
||||||||||
private_node_handle_.param("scale_process_noise", scale_process_noise_, scale_process_noise_); | ||||||||||
|
||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the extra lines for the others are only because they need extra work other than just calling
Suggested change
|
||||||||||
private_node_handle_.param("velocity_norm_min", velocity_norm_min_, velocity_norm_min_); | ||||||||||
|
||||||||||
private_node_handle_.param("disable_checks", disable_checks_, disable_checks_); | ||||||||||
|
@@ -228,23 +229,41 @@ void Unicycle2D::onStart() | |||||||||
state_history_.clear(); | ||||||||||
} | ||||||||||
|
||||||||||
void Unicycle2D::generateMotionModelGenerator( | ||||||||||
const ros::Time& beginning_stamp, | ||||||||||
const ros::Time& ending_stamp, | ||||||||||
std::vector<fuse_core::Constraint::SharedPtr>& constraints, | ||||||||||
std::vector<fuse_core::Variable::SharedPtr>& variables) | ||||||||||
{ | ||||||||||
generateMotionModel(beginning_stamp, ending_stamp, state_history_, device_id_, name(), | ||||||||||
process_noise_covariance_, scale_process_noise_, velocity_norm_min_, disable_checks_, | ||||||||||
constraints, variables); | ||||||||||
} | ||||||||||
|
||||||||||
void Unicycle2D::generateMotionModel( | ||||||||||
const ros::Time& beginning_stamp, | ||||||||||
const ros::Time& ending_stamp, | ||||||||||
StateHistory& state_history, | ||||||||||
const fuse_core::UUID& device_id, | ||||||||||
const std::string name, | ||||||||||
fuse_core::Matrix8d process_noise_covariance, | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you really need to copy here? Why don't you do the following?:
Suggested change
It looks like you're relying on making a copy of the process noise covariance, so the input doesn't change. I believe it's cleaner to just create a local copy below because there are cases where we return even before using the process nosie covariance, so there's no need to copy always it. |
||||||||||
const bool scale_process_noise, | ||||||||||
const double velocity_norm_min, | ||||||||||
const bool disable_checks, | ||||||||||
std::vector<fuse_core::Constraint::SharedPtr>& constraints, | ||||||||||
std::vector<fuse_core::Variable::SharedPtr>& variables) | ||||||||||
{ | ||||||||||
assert(beginning_stamp < ending_stamp || (beginning_stamp == ending_stamp && state_history_.empty())); | ||||||||||
assert(beginning_stamp < ending_stamp || (beginning_stamp == ending_stamp && state_history.empty())); | ||||||||||
|
||||||||||
StateHistoryElement base_state; | ||||||||||
ros::Time base_time; | ||||||||||
|
||||||||||
// Find an entry that is > beginning_stamp | ||||||||||
// The entry that is <= will be the one before it | ||||||||||
auto base_state_pair_it = state_history_.upper_bound(beginning_stamp); | ||||||||||
if (base_state_pair_it == state_history_.begin()) | ||||||||||
auto base_state_pair_it = state_history.upper_bound(beginning_stamp); | ||||||||||
if (base_state_pair_it == state_history.begin()) | ||||||||||
{ | ||||||||||
ROS_WARN_STREAM_COND_NAMED(!state_history_.empty(), "UnicycleModel", "Unable to locate a state in this history " | ||||||||||
ROS_WARN_STREAM_COND_NAMED(!state_history.empty(), "UnicycleModel", "Unable to locate a state in this history " | ||||||||||
"with stamp <= " << beginning_stamp << ". Variables will all be initialized to 0."); | ||||||||||
base_time = beginning_stamp; | ||||||||||
} | ||||||||||
|
@@ -281,13 +300,13 @@ void Unicycle2D::generateMotionModel( | |||||||||
|
||||||||||
if (dt == 0.0) | ||||||||||
{ | ||||||||||
state1.position_uuid = fuse_variables::Position2DStamped(beginning_stamp, device_id_).uuid(); | ||||||||||
state1.yaw_uuid = fuse_variables::Orientation2DStamped(beginning_stamp, device_id_).uuid(); | ||||||||||
state1.vel_linear_uuid = fuse_variables::VelocityLinear2DStamped(beginning_stamp, device_id_).uuid(); | ||||||||||
state1.vel_yaw_uuid = fuse_variables::VelocityAngular2DStamped(beginning_stamp, device_id_).uuid(); | ||||||||||
state1.acc_linear_uuid = fuse_variables::AccelerationLinear2DStamped(beginning_stamp, device_id_).uuid(); | ||||||||||
state1.position_uuid = fuse_variables::Position2DStamped(beginning_stamp, device_id).uuid(); | ||||||||||
state1.yaw_uuid = fuse_variables::Orientation2DStamped(beginning_stamp, device_id).uuid(); | ||||||||||
state1.vel_linear_uuid = fuse_variables::VelocityLinear2DStamped(beginning_stamp, device_id).uuid(); | ||||||||||
state1.vel_yaw_uuid = fuse_variables::VelocityAngular2DStamped(beginning_stamp, device_id).uuid(); | ||||||||||
state1.acc_linear_uuid = fuse_variables::AccelerationLinear2DStamped(beginning_stamp, device_id).uuid(); | ||||||||||
|
||||||||||
state_history_.emplace(beginning_stamp, std::move(state1)); | ||||||||||
state_history.emplace(beginning_stamp, std::move(state1)); | ||||||||||
|
||||||||||
return; | ||||||||||
} | ||||||||||
|
@@ -306,16 +325,16 @@ void Unicycle2D::generateMotionModel( | |||||||||
state2.acceleration_linear); | ||||||||||
|
||||||||||
// Define the fuse variables required for this constraint | ||||||||||
auto position1 = fuse_variables::Position2DStamped::make_shared(beginning_stamp, device_id_); | ||||||||||
auto yaw1 = fuse_variables::Orientation2DStamped::make_shared(beginning_stamp, device_id_); | ||||||||||
auto velocity_linear1 = fuse_variables::VelocityLinear2DStamped::make_shared(beginning_stamp, device_id_); | ||||||||||
auto velocity_yaw1 = fuse_variables::VelocityAngular2DStamped::make_shared(beginning_stamp, device_id_); | ||||||||||
auto acceleration_linear1 = fuse_variables::AccelerationLinear2DStamped::make_shared(beginning_stamp, device_id_); | ||||||||||
auto position2 = fuse_variables::Position2DStamped::make_shared(ending_stamp, device_id_); | ||||||||||
auto yaw2 = fuse_variables::Orientation2DStamped::make_shared(ending_stamp, device_id_); | ||||||||||
auto velocity_linear2 = fuse_variables::VelocityLinear2DStamped::make_shared(ending_stamp, device_id_); | ||||||||||
auto velocity_yaw2 = fuse_variables::VelocityAngular2DStamped::make_shared(ending_stamp, device_id_); | ||||||||||
auto acceleration_linear2 = fuse_variables::AccelerationLinear2DStamped::make_shared(ending_stamp, device_id_); | ||||||||||
auto position1 = fuse_variables::Position2DStamped::make_shared(beginning_stamp, device_id); | ||||||||||
auto yaw1 = fuse_variables::Orientation2DStamped::make_shared(beginning_stamp, device_id); | ||||||||||
auto velocity_linear1 = fuse_variables::VelocityLinear2DStamped::make_shared(beginning_stamp, device_id); | ||||||||||
auto velocity_yaw1 = fuse_variables::VelocityAngular2DStamped::make_shared(beginning_stamp, device_id); | ||||||||||
auto acceleration_linear1 = fuse_variables::AccelerationLinear2DStamped::make_shared(beginning_stamp, device_id); | ||||||||||
auto position2 = fuse_variables::Position2DStamped::make_shared(ending_stamp, device_id); | ||||||||||
auto yaw2 = fuse_variables::Orientation2DStamped::make_shared(ending_stamp, device_id); | ||||||||||
auto velocity_linear2 = fuse_variables::VelocityLinear2DStamped::make_shared(ending_stamp, device_id); | ||||||||||
auto velocity_yaw2 = fuse_variables::VelocityAngular2DStamped::make_shared(ending_stamp, device_id); | ||||||||||
auto acceleration_linear2 = fuse_variables::AccelerationLinear2DStamped::make_shared(ending_stamp, device_id); | ||||||||||
|
||||||||||
position1->data()[fuse_variables::Position2DStamped::X] = state1.pose.x(); | ||||||||||
position1->data()[fuse_variables::Position2DStamped::Y] = state1.pose.y(); | ||||||||||
|
@@ -345,36 +364,42 @@ void Unicycle2D::generateMotionModel( | |||||||||
state2.vel_yaw_uuid = velocity_yaw2->uuid(); | ||||||||||
state2.acc_linear_uuid = acceleration_linear2->uuid(); | ||||||||||
|
||||||||||
state_history_.emplace(beginning_stamp, std::move(state1)); | ||||||||||
state_history_.emplace(ending_stamp, std::move(state2)); | ||||||||||
state_history.emplace(beginning_stamp, std::move(state1)); | ||||||||||
state_history.emplace(ending_stamp, std::move(state2)); | ||||||||||
|
||||||||||
// Rotate the process noise covariance with the yaw angle of the current state orientation | ||||||||||
const auto rotation_matrix = Eigen::Rotation2Dd(state1.pose.yaw()).toRotationMatrix(); | ||||||||||
// Apply only to x and y position as the other state variables are already along the | ||||||||||
// current state orientation | ||||||||||
process_noise_covariance.topLeftCorner<2, 2>() = | ||||||||||
rotation_matrix * process_noise_covariance.topLeftCorner<2, 2>() * rotation_matrix.transpose(); | ||||||||||
Comment on lines
+352
to
+357
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little confused with this. The actual residual that is computed for the unicycle constraints is just the position difference. That difference is expressed in the world/map frame here, so rotating the process noise into the frame doesn't make sense to me. This would make sense if that residual is also expressed in the first poses frame. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @svwilliams Has your team had a chance to validate or test this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked at some point, but haven't had time to actually fix it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that makes more sense to me. I can work on a PR that does that |
||||||||||
|
||||||||||
// Scale process noise covariance pose by the norm of the current state twist | ||||||||||
auto process_noise_covariance = process_noise_covariance_; | ||||||||||
if (scale_process_noise_) | ||||||||||
if (scale_process_noise) | ||||||||||
{ | ||||||||||
common::scaleProcessNoiseCovariance(process_noise_covariance, state1.velocity_linear, state1.velocity_yaw, | ||||||||||
velocity_norm_min_); | ||||||||||
velocity_norm_min); | ||||||||||
} | ||||||||||
|
||||||||||
// Validate | ||||||||||
process_noise_covariance *= dt; | ||||||||||
|
||||||||||
if (!disable_checks_) | ||||||||||
if (!disable_checks) | ||||||||||
{ | ||||||||||
try | ||||||||||
{ | ||||||||||
validateMotionModel(state1, state2, process_noise_covariance); | ||||||||||
} | ||||||||||
catch (const std::runtime_error& ex) | ||||||||||
{ | ||||||||||
ROS_ERROR_STREAM_THROTTLE(10.0, "Invalid '" << name_ << "' motion model: " << ex.what()); | ||||||||||
ROS_ERROR_STREAM_THROTTLE(10.0, "Invalid '" << name << "' motion model: " << ex.what()); | ||||||||||
return; | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
// Create the constraints for this motion model segment | ||||||||||
auto constraint = fuse_models::Unicycle2DStateKinematicConstraint::make_shared( | ||||||||||
name(), | ||||||||||
name, | ||||||||||
*position1, | ||||||||||
*yaw1, | ||||||||||
*velocity_linear1, | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to be reworded, maybe something like: