ros_filter.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014, 2015, 2016, Charles River Analytics, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following
13  * disclaimer in the documentation and/or other materials provided
14  * with the distribution.
15  * 3. Neither the name of the copyright holder nor the names of its
16  * contributors may be used to endorse or promote products derived
17  * from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
35 #include "robot_localization/ekf.h"
36 #include "robot_localization/ukf.h"
37 
39 
40 #include <algorithm>
41 #include <map>
42 #include <string>
43 #include <utility>
44 #include <vector>
45 #include <limits>
46 
47 namespace RobotLocalization
48 {
49  template<typename T>
50  RosFilter<T>::RosFilter(std::vector<double> args) :
51  staticDiagErrorLevel_(diagnostic_msgs::DiagnosticStatus::OK),
52  tfListener_(tfBuffer_),
53  dynamicDiagErrorLevel_(diagnostic_msgs::DiagnosticStatus::OK),
54  filter_(args),
55  frequency_(30.0),
56  historyLength_(0),
57  lastSetPoseTime_(0),
58  latestControl_(),
59  latestControlTime_(0),
60  tfTimeout_(ros::Duration(0)),
61  nhLocal_("~"),
62  predictToCurrentTime_(false),
63  printDiagnostics_(true),
64  gravitationalAcc_(9.80665),
65  publishTransform_(true),
66  publishAcceleration_(false),
67  twoDMode_(false),
68  useControl_(false),
69  smoothLaggedData_(false),
70  disabledAtStartup_(false),
71  enabled_(false),
72  toggledOn_(true)
73  {
74  stateVariableNames_.push_back("X");
75  stateVariableNames_.push_back("Y");
76  stateVariableNames_.push_back("Z");
77  stateVariableNames_.push_back("ROLL");
78  stateVariableNames_.push_back("PITCH");
79  stateVariableNames_.push_back("YAW");
80  stateVariableNames_.push_back("X_VELOCITY");
81  stateVariableNames_.push_back("Y_VELOCITY");
82  stateVariableNames_.push_back("Z_VELOCITY");
83  stateVariableNames_.push_back("ROLL_VELOCITY");
84  stateVariableNames_.push_back("PITCH_VELOCITY");
85  stateVariableNames_.push_back("YAW_VELOCITY");
86  stateVariableNames_.push_back("X_ACCELERATION");
87  stateVariableNames_.push_back("Y_ACCELERATION");
88  stateVariableNames_.push_back("Z_ACCELERATION");
89 
91  }
92 
93  template<typename T>
95  {
96  topicSubs_.clear();
97  }
98 
99  template<typename T>
101  {
102  // Get rid of any initial poses (pretend we've never had a measurement)
103  initialMeasurements_.clear();
104  previousMeasurements_.clear();
106 
108 
109  filterStateHistory_.clear();
110  measurementHistory_.clear();
111 
112  // Also set the last set pose time, so we ignore all messages
113  // that occur before it
115 
116  // clear tf buffer to avoid TF_OLD_DATA errors
117  tfBuffer_.clear();
118 
119  // clear last message timestamp, so older messages will be accepted
120  lastMessageTimes_.clear();
121 
122  // reset filter to uninitialized state
123  filter_.reset();
124 
125  // clear all waiting callbacks
127  }
128 
129  template<typename T>
130  bool RosFilter<T>::toggleFilterProcessingCallback(robot_localization::ToggleFilterProcessing::Request& req,
131  robot_localization::ToggleFilterProcessing::Response& resp)
132  {
133  if (req.on == toggledOn_)
134  {
135  ROS_WARN_STREAM("Service was called to toggle filter processing but state was already as requested.");
136  resp.status = false;
137  }
138  else
139  {
140  ROS_INFO("Toggling filter measurement filtering to %s.", req.on ? "On" : "Off");
141  toggledOn_ = req.on;
142  resp.status = true;
143  }
144  return true;
145  }
146 
147  // @todo: Replace with AccelWithCovarianceStamped
148  template<typename T>
149  void RosFilter<T>::accelerationCallback(const sensor_msgs::Imu::ConstPtr &msg, const CallbackData &callbackData,
150  const std::string &targetFrame)
151  {
152  // If we've just reset the filter, then we want to ignore any messages
153  // that arrive with an older timestamp
154  if (msg->header.stamp <= lastSetPoseTime_)
155  {
156  return;
157  }
158 
159  const std::string &topicName = callbackData.topicName_;
160 
161  RF_DEBUG("------ RosFilter::accelerationCallback (" << topicName << ") ------\n"
162  "Twist message:\n" << *msg);
163 
164  if (lastMessageTimes_.count(topicName) == 0)
165  {
166  lastMessageTimes_.insert(std::pair<std::string, ros::Time>(topicName, msg->header.stamp));
167  }
168 
169  // Make sure this message is newer than the last one
170  if (msg->header.stamp >= lastMessageTimes_[topicName])
171  {
172  RF_DEBUG("Update vector for " << topicName << " is:\n" << topicName);
173 
174  Eigen::VectorXd measurement(STATE_SIZE);
175  Eigen::MatrixXd measurementCovariance(STATE_SIZE, STATE_SIZE);
176 
177  measurement.setZero();
178  measurementCovariance.setZero();
179 
180  // Make sure we're actually updating at least one of these variables
181  std::vector<int> updateVectorCorrected = callbackData.updateVector_;
182 
183  // Prepare the twist data for inclusion in the filter
184  if (prepareAcceleration(msg, topicName, targetFrame, updateVectorCorrected, measurement,
185  measurementCovariance))
186  {
187  // Store the measurement. Add an "acceleration" suffix so we know what kind of measurement
188  // we're dealing with when we debug the core filter logic.
189  enqueueMeasurement(topicName,
190  measurement,
191  measurementCovariance,
192  updateVectorCorrected,
193  callbackData.rejectionThreshold_,
194  msg->header.stamp);
195 
196  RF_DEBUG("Enqueued new measurement for " << topicName << "_acceleration\n");
197  }
198  else
199  {
200  RF_DEBUG("Did *not* enqueue measurement for " << topicName << "_acceleration\n");
201  }
202 
203  lastMessageTimes_[topicName] = msg->header.stamp;
204 
205  RF_DEBUG("Last message time for " << topicName << " is now " <<
206  lastMessageTimes_[topicName] << "\n");
207  }
209  {
210  reset();
211  }
212  else
213  {
214  std::stringstream stream;
215  stream << "The " << topicName << " message has a timestamp before that of the previous message received," <<
216  " this message will be ignored. This may indicate a bad timestamp. (message time: " <<
217  msg->header.stamp.toSec() << ")";
218  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
219  topicName + "_timestamp",
220  stream.str(),
221  false);
222 
223  RF_DEBUG("Message is too old. Last message time for " << topicName <<
224  " is " << lastMessageTimes_[topicName] << ", current message time is " <<
225  msg->header.stamp << ".\n");
226  }
227 
228  RF_DEBUG("\n----- /RosFilter::accelerationCallback (" << topicName << ") ------\n");
229  }
230 
231  template<typename T>
232  void RosFilter<T>::controlCallback(const geometry_msgs::Twist::ConstPtr &msg)
233  {
234  geometry_msgs::TwistStampedPtr twistStampedPtr = geometry_msgs::TwistStampedPtr(new geometry_msgs::TwistStamped());
235  twistStampedPtr->twist = *msg;
236  twistStampedPtr->header.frame_id = baseLinkFrameId_;
237  twistStampedPtr->header.stamp = ros::Time::now();
238  controlCallback(twistStampedPtr);
239  }
240 
241  template<typename T>
242  void RosFilter<T>::controlCallback(const geometry_msgs::TwistStamped::ConstPtr &msg)
243  {
244  if (msg->header.frame_id == baseLinkFrameId_ || msg->header.frame_id == "")
245  {
246  latestControl_(ControlMemberVx) = msg->twist.linear.x;
247  latestControl_(ControlMemberVy) = msg->twist.linear.y;
248  latestControl_(ControlMemberVz) = msg->twist.linear.z;
249  latestControl_(ControlMemberVroll) = msg->twist.angular.x;
250  latestControl_(ControlMemberVpitch) = msg->twist.angular.y;
251  latestControl_(ControlMemberVyaw) = msg->twist.angular.z;
252  latestControlTime_ = msg->header.stamp;
253 
254  // Update the filter with this control term
255  filter_.setControl(latestControl_, msg->header.stamp.toSec());
256  }
257  else
258  {
259  ROS_WARN_STREAM_THROTTLE(5.0, "Commanded velocities must be given in the robot's body frame (" <<
260  baseLinkFrameId_ << "). Message frame was " << msg->header.frame_id);
261  }
262  }
263 
264  template<typename T>
265  void RosFilter<T>::enqueueMeasurement(const std::string &topicName,
266  const Eigen::VectorXd &measurement,
267  const Eigen::MatrixXd &measurementCovariance,
268  const std::vector<int> &updateVector,
269  const double mahalanobisThresh,
270  const ros::Time &time)
271  {
273 
274  meas->topicName_ = topicName;
275  meas->measurement_ = measurement;
276  meas->covariance_ = measurementCovariance;
277  meas->updateVector_ = updateVector;
278  meas->time_ = time.toSec();
279  meas->mahalanobisThresh_ = mahalanobisThresh;
280  meas->latestControl_ = latestControl_;
281  meas->latestControlTime_ = latestControlTime_.toSec();
282  measurementQueue_.push(meas);
283  }
284 
285  template<typename T>
286  void RosFilter<T>::forceTwoD(Eigen::VectorXd &measurement,
287  Eigen::MatrixXd &measurementCovariance,
288  std::vector<int> &updateVector)
289  {
290  measurement(StateMemberZ) = 0.0;
291  measurement(StateMemberRoll) = 0.0;
292  measurement(StateMemberPitch) = 0.0;
293  measurement(StateMemberVz) = 0.0;
294  measurement(StateMemberVroll) = 0.0;
295  measurement(StateMemberVpitch) = 0.0;
296  measurement(StateMemberAz) = 0.0;
297 
298  measurementCovariance(StateMemberZ, StateMemberZ) = 1e-6;
299  measurementCovariance(StateMemberRoll, StateMemberRoll) = 1e-6;
300  measurementCovariance(StateMemberPitch, StateMemberPitch) = 1e-6;
301  measurementCovariance(StateMemberVz, StateMemberVz) = 1e-6;
302  measurementCovariance(StateMemberVroll, StateMemberVroll) = 1e-6;
303  measurementCovariance(StateMemberVpitch, StateMemberVpitch) = 1e-6;
304  measurementCovariance(StateMemberAz, StateMemberAz) = 1e-6;
305 
306  updateVector[StateMemberZ] = 1;
307  updateVector[StateMemberRoll] = 1;
308  updateVector[StateMemberPitch] = 1;
309  updateVector[StateMemberVz] = 1;
310  updateVector[StateMemberVroll] = 1;
311  updateVector[StateMemberVpitch] = 1;
312  updateVector[StateMemberAz] = 1;
313  }
314 
315  template<typename T>
316  bool RosFilter<T>::getFilteredOdometryMessage(nav_msgs::Odometry &message)
317  {
318  // If the filter has received a measurement at some point...
319  if (filter_.getInitializedStatus())
320  {
321  // Grab our current state and covariance estimates
322  const Eigen::VectorXd &state = filter_.getState();
323  const Eigen::MatrixXd &estimateErrorCovariance = filter_.getEstimateErrorCovariance();
324 
325  // Convert from roll, pitch, and yaw back to quaternion for
326  // orientation values
327  tf2::Quaternion quat;
328  quat.setRPY(state(StateMemberRoll), state(StateMemberPitch), state(StateMemberYaw));
329 
330  // Fill out the message
331  message.pose.pose.position.x = state(StateMemberX);
332  message.pose.pose.position.y = state(StateMemberY);
333  message.pose.pose.position.z = state(StateMemberZ);
334  message.pose.pose.orientation.x = quat.x();
335  message.pose.pose.orientation.y = quat.y();
336  message.pose.pose.orientation.z = quat.z();
337  message.pose.pose.orientation.w = quat.w();
338  message.twist.twist.linear.x = state(StateMemberVx);
339  message.twist.twist.linear.y = state(StateMemberVy);
340  message.twist.twist.linear.z = state(StateMemberVz);
341  message.twist.twist.angular.x = state(StateMemberVroll);
342  message.twist.twist.angular.y = state(StateMemberVpitch);
343  message.twist.twist.angular.z = state(StateMemberVyaw);
344 
345  // Our covariance matrix layout doesn't quite match
346  for (size_t i = 0; i < POSE_SIZE; i++)
347  {
348  for (size_t j = 0; j < POSE_SIZE; j++)
349  {
350  message.pose.covariance[POSE_SIZE * i + j] = estimateErrorCovariance(i, j);
351  }
352  }
353 
354  // POSE_SIZE and TWIST_SIZE are currently the same size, but we can spare a few
355  // cycles to be meticulous and not index a twist covariance array on the size of
356  // a pose covariance array
357  for (size_t i = 0; i < TWIST_SIZE; i++)
358  {
359  for (size_t j = 0; j < TWIST_SIZE; j++)
360  {
361  message.twist.covariance[TWIST_SIZE * i + j] =
362  estimateErrorCovariance(i + POSITION_V_OFFSET, j + POSITION_V_OFFSET);
363  }
364  }
365 
366  message.header.stamp = ros::Time(filter_.getLastMeasurementTime());
367  message.header.frame_id = worldFrameId_;
368  message.child_frame_id = baseLinkOutputFrameId_;
369  }
370 
371  return filter_.getInitializedStatus();
372  }
373 
374  template<typename T>
375  bool RosFilter<T>::getFilteredAccelMessage(geometry_msgs::AccelWithCovarianceStamped &message)
376  {
377  // If the filter has received a measurement at some point...
378  if (filter_.getInitializedStatus())
379  {
380  // Grab our current state and covariance estimates
381  const Eigen::VectorXd &state = filter_.getState();
382  const Eigen::MatrixXd &estimateErrorCovariance = filter_.getEstimateErrorCovariance();
383 
385  message.accel.accel.linear.x = state(StateMemberAx);
386  message.accel.accel.linear.y = state(StateMemberAy);
387  message.accel.accel.linear.z = state(StateMemberAz);
388 
389  // Fill the covariance (only the left-upper matrix since we are not estimating
390  // the rotational accelerations arround the axes
391  for (size_t i = 0; i < ACCELERATION_SIZE; i++)
392  {
393  for (size_t j = 0; j < ACCELERATION_SIZE; j++)
394  {
395  // We use the POSE_SIZE since the accel cov matrix of ROS is 6x6
396  message.accel.covariance[POSE_SIZE * i + j] =
397  estimateErrorCovariance(i + POSITION_A_OFFSET, j + POSITION_A_OFFSET);
398  }
399  }
400 
401  // Fill header information
402  message.header.stamp = ros::Time(filter_.getLastMeasurementTime());
403  message.header.frame_id = baseLinkOutputFrameId_;
404  }
405 
406  return filter_.getInitializedStatus();
407  }
408 
409  template<typename T>
410  void RosFilter<T>::imuCallback(const sensor_msgs::Imu::ConstPtr &msg,
411  const std::string &topicName,
412  const CallbackData &poseCallbackData,
413  const CallbackData &twistCallbackData,
414  const CallbackData &accelCallbackData)
415  {
416  RF_DEBUG("------ RosFilter::imuCallback (" << topicName << ") ------\n" << "IMU message:\n" << *msg);
417 
418  // If we've just reset the filter, then we want to ignore any messages
419  // that arrive with an older timestamp
420  if (msg->header.stamp <= lastSetPoseTime_)
421  {
422  std::stringstream stream;
423  stream << "The " << topicName << " message has a timestamp equal to or before the last filter reset, " <<
424  "this message will be ignored. This may indicate an empty or bad timestamp. (message time: " <<
425  msg->header.stamp.toSec() << ")";
426  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
427  topicName + "_timestamp",
428  stream.str(),
429  false);
430  RF_DEBUG("Received message that preceded the most recent pose reset. Ignoring...");
431 
432  return;
433  }
434 
435  // As with the odometry message, we can separate out the pose- and twist-related variables
436  // in the IMU message and pass them to the pose and twist callbacks (filters)
437  if (poseCallbackData.updateSum_ > 0)
438  {
439  // Per the IMU message specification, if the IMU does not provide orientation,
440  // then its first covariance value should be set to -1, and we should ignore
441  // that portion of the message. robot_localization allows users to explicitly
442  // ignore data using its parameters, but we should also be compliant with
443  // message specs.
444  if (::fabs(msg->orientation_covariance[0] + 1) < 1e-9)
445  {
446  RF_DEBUG("Received IMU message with -1 as its first covariance value for orientation. "
447  "Ignoring orientation...");
448  }
449  else
450  {
451  // Extract the pose (orientation) data, pass it to its filter
452  geometry_msgs::PoseWithCovarianceStamped *posPtr = new geometry_msgs::PoseWithCovarianceStamped();
453  posPtr->header = msg->header;
454  posPtr->pose.pose.orientation = msg->orientation;
455 
456  // Copy the covariance for roll, pitch, and yaw
457  for (size_t i = 0; i < ORIENTATION_SIZE; i++)
458  {
459  for (size_t j = 0; j < ORIENTATION_SIZE; j++)
460  {
461  posPtr->pose.covariance[POSE_SIZE * (i + ORIENTATION_SIZE) + (j + ORIENTATION_SIZE)] =
462  msg->orientation_covariance[ORIENTATION_SIZE * i + j];
463  }
464  }
465 
466  // IMU data gets handled a bit differently, since the message is ambiguous and has only a single frame_id,
467  // even though the data in it is reported in two different frames. As we assume users will specify a base_link
468  // to imu transform, we make the target frame baseLinkFrameId_ and tell the poseCallback that it is working
469  // with IMU data. This will cause it to apply different logic to the data.
470  geometry_msgs::PoseWithCovarianceStampedConstPtr pptr(posPtr);
471  poseCallback(pptr, poseCallbackData, baseLinkFrameId_, true);
472  }
473  }
474 
475  if (twistCallbackData.updateSum_ > 0)
476  {
477  // Ignore rotational velocity if the first covariance value is -1
478  if (::fabs(msg->angular_velocity_covariance[0] + 1) < 1e-9)
479  {
480  RF_DEBUG("Received IMU message with -1 as its first covariance value for angular "
481  "velocity. Ignoring angular velocity...");
482  }
483  else
484  {
485  // Repeat for velocity
486  geometry_msgs::TwistWithCovarianceStamped *twistPtr = new geometry_msgs::TwistWithCovarianceStamped();
487  twistPtr->header = msg->header;
488  twistPtr->twist.twist.angular = msg->angular_velocity;
489 
490  // Copy the covariance
491  for (size_t i = 0; i < ORIENTATION_SIZE; i++)
492  {
493  for (size_t j = 0; j < ORIENTATION_SIZE; j++)
494  {
495  twistPtr->twist.covariance[TWIST_SIZE * (i + ORIENTATION_SIZE) + (j + ORIENTATION_SIZE)] =
496  msg->angular_velocity_covariance[ORIENTATION_SIZE * i + j];
497  }
498  }
499 
500  geometry_msgs::TwistWithCovarianceStampedConstPtr tptr(twistPtr);
501  twistCallback(tptr, twistCallbackData, baseLinkFrameId_);
502  }
503  }
504 
505  if (accelCallbackData.updateSum_ > 0)
506  {
507  // Ignore linear acceleration if the first covariance value is -1
508  if (::fabs(msg->linear_acceleration_covariance[0] + 1) < 1e-9)
509  {
510  RF_DEBUG("Received IMU message with -1 as its first covariance value for linear "
511  "acceleration. Ignoring linear acceleration...");
512  }
513  else
514  {
515  // Pass the message on
516  accelerationCallback(msg, accelCallbackData, baseLinkFrameId_);
517  }
518  }
519 
520  RF_DEBUG("\n----- /RosFilter::imuCallback (" << topicName << ") ------\n");
521  }
522 
523  template<typename T>
525  {
526  const double currentTimeSec = currentTime.toSec();
527 
528  RF_DEBUG("------ RosFilter::integrateMeasurements ------\n\n"
529  "Integration time is " << std::setprecision(20) << currentTimeSec << "\n"
530  << measurementQueue_.size() << " measurements in queue.\n");
531 
532  bool predictToCurrentTime = predictToCurrentTime_;
533 
534  // If we have any measurements in the queue, process them
535  if (!measurementQueue_.empty())
536  {
537  // Check if the first measurement we're going to process is older than the filter's last measurement.
538  // This means we have received an out-of-sequence message (one with an old timestamp), and we need to
539  // revert both the filter state and measurement queue to the first state that preceded the time stamp
540  // of our first measurement.
541  const MeasurementPtr& firstMeasurement = measurementQueue_.top();
542  int restoredMeasurementCount = 0;
543  if (smoothLaggedData_ && firstMeasurement->time_ < filter_.getLastMeasurementTime())
544  {
545  RF_DEBUG("Received a measurement that was " << filter_.getLastMeasurementTime() - firstMeasurement->time_ <<
546  " seconds in the past. Reverting filter state and measurement queue...");
547 
548  int originalCount = static_cast<int>(measurementQueue_.size());
549  const double firstMeasurementTime = firstMeasurement->time_;
550  const std::string firstMeasurementTopic = firstMeasurement->topicName_;
551  if (!revertTo(firstMeasurementTime - 1e-9)) // revertTo may invalidate firstMeasurement
552  {
553  RF_DEBUG("ERROR: history interval is too small to revert to time " << firstMeasurementTime << "\n");
554  ROS_WARN_STREAM_DELAYED_THROTTLE(historyLength_, "Received old measurement for topic " <<
555  firstMeasurementTopic << ", but history interval is insufficiently sized. Measurement time is " <<
556  std::setprecision(20) << firstMeasurementTime << ", current time is " << currentTime.toSec() <<
557  ", history length is " << historyLength_ << ".");
558  restoredMeasurementCount = 0;
559  }
560 
561  restoredMeasurementCount = static_cast<int>(measurementQueue_.size()) - originalCount;
562  }
563 
564  while (!measurementQueue_.empty() && ros::ok())
565  {
566  MeasurementPtr measurement = measurementQueue_.top();
567 
568  // If we've reached a measurement that has a time later than now, it should wait until a future iteration.
569  // Since measurements are stored in a priority queue, all remaining measurements will be in the future.
570  if (measurement->time_ > currentTime.toSec())
571  {
572  break;
573  }
574 
575  measurementQueue_.pop();
576 
577  // When we receive control messages, we call this directly in the control callback. However, we also associate
578  // a control with each sensor message so that we can support lagged smoothing. As we cannot guarantee that the
579  // new control callback will fire before a new measurement, we should only perform this operation if we are
580  // processing messages from the history. Otherwise, we may get a new measurement, store the "old" latest
581  // control, then receive a control, call setControl, and then overwrite that value with this one (i.e., with
582  // the "old" control we associated with the measurement).
583  if (useControl_ && restoredMeasurementCount > 0)
584  {
585  filter_.setControl(measurement->latestControl_, measurement->latestControlTime_);
586  restoredMeasurementCount--;
587  }
588 
589  // This will call predict and, if necessary, correct
590  filter_.processMeasurement(*(measurement.get()));
591 
592  // Store old states and measurements if we're smoothing
593  if (smoothLaggedData_)
594  {
595  // Invariant still holds: measurementHistoryDeque_.back().time_ < measurementQueue_.top().time_
596  measurementHistory_.push_back(measurement);
597 
598  // We should only save the filter state once per unique timstamp
599  if (measurementQueue_.empty() ||
600  ::fabs(measurementQueue_.top()->time_ - filter_.getLastMeasurementTime()) > 1e-9)
601  {
603  }
604  }
605  }
606  }
607  else if (filter_.getInitializedStatus())
608  {
609  // In the event that we don't get any measurements for a long time,
610  // we still need to continue to estimate our state. Therefore, we
611  // should project the state forward here.
612  double lastUpdateDelta = currentTimeSec - filter_.getLastMeasurementTime();
613 
614  // If we get a large delta, then continuously predict until
615  if (lastUpdateDelta >= filter_.getSensorTimeout())
616  {
617  predictToCurrentTime = true;
618 
619  RF_DEBUG("Sensor timeout! Last measurement time was " << filter_.getLastMeasurementTime() <<
620  ", current time is " << currentTimeSec <<
621  ", delta is " << lastUpdateDelta << "\n");
622  }
623  }
624  else
625  {
626  RF_DEBUG("Filter not yet initialized.\n");
627  }
628 
629  if (filter_.getInitializedStatus() && predictToCurrentTime)
630  {
631  double lastUpdateDelta = currentTimeSec - filter_.getLastMeasurementTime();
632 
633  filter_.validateDelta(lastUpdateDelta);
634  filter_.predict(currentTimeSec, lastUpdateDelta);
635 
636  // Update the last measurement time and last update time
637  filter_.setLastMeasurementTime(filter_.getLastMeasurementTime() + lastUpdateDelta);
638  }
639 
640  RF_DEBUG("\n----- /RosFilter::integrateMeasurements ------\n");
641  }
642 
643  template<typename T>
645  {
646  /* For diagnostic purposes, collect information about how many different
647  * sources are measuring each absolute pose variable and do not have
648  * differential integration enabled.
649  */
650  std::map<StateMembers, int> absPoseVarCounts;
651  absPoseVarCounts[StateMemberX] = 0;
652  absPoseVarCounts[StateMemberY] = 0;
653  absPoseVarCounts[StateMemberZ] = 0;
654  absPoseVarCounts[StateMemberRoll] = 0;
655  absPoseVarCounts[StateMemberPitch] = 0;
656  absPoseVarCounts[StateMemberYaw] = 0;
657 
658  // Same for twist variables
659  std::map<StateMembers, int> twistVarCounts;
660  twistVarCounts[StateMemberVx] = 0;
661  twistVarCounts[StateMemberVy] = 0;
662  twistVarCounts[StateMemberVz] = 0;
663  twistVarCounts[StateMemberVroll] = 0;
664  twistVarCounts[StateMemberVpitch] = 0;
665  twistVarCounts[StateMemberVyaw] = 0;
666 
667  // Determine if we'll be printing diagnostic information
668  nhLocal_.param("print_diagnostics", printDiagnostics_, true);
669 
670  // Check for custom gravitational acceleration value
671  nhLocal_.param("gravitational_acceleration", gravitationalAcc_, 9.80665);
672 
673  // Grab the debug param. If true, the node will produce a LOT of output.
674  bool debug;
675  nhLocal_.param("debug", debug, false);
676 
677  if (debug)
678  {
679  std::string debugOutFile;
680 
681  try
682  {
683  nhLocal_.param("debug_out_file", debugOutFile, std::string("robot_localization_debug.txt"));
684  debugStream_.open(debugOutFile.c_str());
685 
686  // Make sure we succeeded
687  if (debugStream_.is_open())
688  {
689  filter_.setDebug(debug, &debugStream_);
690  }
691  else
692  {
693  ROS_WARN_STREAM("RosFilter::loadParams() - unable to create debug output file " << debugOutFile);
694  }
695  }
696  catch(const std::exception &e)
697  {
698  ROS_WARN_STREAM("RosFilter::loadParams() - unable to create debug output file" << debugOutFile
699  << ". Error was " << e.what() << "\n");
700  }
701  }
702 
703  // These params specify the name of the robot's body frame (typically
704  // base_link) and odometry frame (typically odom)
705  nhLocal_.param("map_frame", mapFrameId_, std::string("map"));
706  nhLocal_.param("odom_frame", odomFrameId_, std::string("odom"));
707  nhLocal_.param("base_link_frame", baseLinkFrameId_, std::string("base_link"));
708  nhLocal_.param("base_link_frame_output", baseLinkOutputFrameId_, baseLinkFrameId_);
709 
710  /*
711  * These parameters are designed to enforce compliance with REP-105:
712  * http://www.ros.org/reps/rep-0105.html
713  * When fusing absolute position data from sensors such as GPS, the state
714  * estimate can undergo discrete jumps. According to REP-105, we want three
715  * coordinate frames: map, odom, and base_link. The map frame can have
716  * discontinuities, but is the frame with the most accurate position estimate
717  * for the robot and should not suffer from drift. The odom frame drifts over
718  * time, but is guaranteed to be continuous and is accurate enough for local
719  * planning and navigation. The base_link frame is affixed to the robot. The
720  * intention is that some odometry source broadcasts the odom->base_link
721  * transform. The localization software should broadcast map->base_link.
722  * However, tf does not allow multiple parents for a coordinate frame, so
723  * we must *compute* map->base_link, but then use the existing odom->base_link
724  * transform to compute *and broadcast* map->odom.
725  *
726  * The state estimation nodes in robot_localization therefore have two "modes."
727  * If your world_frame parameter value matches the odom_frame parameter value,
728  * then robot_localization will assume someone else is broadcasting a transform
729  * from odom_frame->base_link_frame, and it will compute the
730  * map_frame->odom_frame transform. Otherwise, it will simply compute the
731  * odom_frame->base_link_frame transform.
732  *
733  * The default is the latter behavior (broadcast of odom->base_link).
734  */
735  nhLocal_.param("world_frame", worldFrameId_, odomFrameId_);
736 
742  "Invalid frame configuration! The values for map_frame, odom_frame, "
743  "and base_link_frame must be unique. If using a base_link_frame_output values, it "
744  "must not match the map_frame or odom_frame.");
745 
746  // Try to resolve tf_prefix
747  std::string tfPrefix = "";
748  std::string tfPrefixPath = "";
749  if (nhLocal_.searchParam("tf_prefix", tfPrefixPath))
750  {
751  nhLocal_.getParam(tfPrefixPath, tfPrefix);
752  }
753 
754  // Append the tf prefix in a tf2-friendly manner
760 
761  // Whether we're publshing the world_frame->base_link_frame transform
762  nhLocal_.param("publish_tf", publishTransform_, true);
763 
764  // Whether we're publishing the acceleration state transform
765  nhLocal_.param("publish_acceleration", publishAcceleration_, false);
766 
767  // Transform future dating
768  double offsetTmp;
769  nhLocal_.param("transform_time_offset", offsetTmp, 0.0);
770  tfTimeOffset_.fromSec(offsetTmp);
771 
772  // Transform timeout
773  double timeoutTmp;
774  nhLocal_.param("transform_timeout", timeoutTmp, 0.0);
775  tfTimeout_.fromSec(timeoutTmp);
776 
777  // Update frequency and sensor timeout
778  double sensorTimeout;
779  nhLocal_.param("frequency", frequency_, 30.0);
780  nhLocal_.param("sensor_timeout", sensorTimeout, 1.0 / frequency_);
781  filter_.setSensorTimeout(sensorTimeout);
782 
783  // Determine if we're in 2D mode
784  nhLocal_.param("two_d_mode", twoDMode_, false);
785 
786  // Smoothing window size
787  nhLocal_.param("smooth_lagged_data", smoothLaggedData_, false);
788  nhLocal_.param("history_length", historyLength_, 0.0);
789 
790  // Wether we reset filter on jump back in time
791  nhLocal_.param("reset_on_time_jump", resetOnTimeJump_, false);
792 
793  if (!smoothLaggedData_ && ::fabs(historyLength_) > 1e-9)
794  {
795  ROS_WARN_STREAM("Filter history interval of " << historyLength_ <<
796  " specified, but smooth_lagged_data is set to false. Lagged data will not be smoothed.");
797  }
798 
799  if (smoothLaggedData_ && historyLength_ < -1e9)
800  {
801  ROS_WARN_STREAM("Negative history interval of " << historyLength_ <<
802  " specified. Absolute value will be assumed.");
803  }
804 
805  historyLength_ = ::fabs(historyLength_);
806 
807  nhLocal_.param("predict_to_current_time", predictToCurrentTime_, false);
808 
809  // Determine if we're using a control term
810  bool stampedControl = false;
811  double controlTimeout = sensorTimeout;
812  std::vector<int> controlUpdateVector(TWIST_SIZE, 0);
813  std::vector<double> accelerationLimits(TWIST_SIZE, 1.0);
814  std::vector<double> accelerationGains(TWIST_SIZE, 1.0);
815  std::vector<double> decelerationLimits(TWIST_SIZE, 1.0);
816  std::vector<double> decelerationGains(TWIST_SIZE, 1.0);
817 
818  nhLocal_.param("use_control", useControl_, false);
819  nhLocal_.param("stamped_control", stampedControl, false);
820  nhLocal_.param("control_timeout", controlTimeout, sensorTimeout);
821 
822  if (useControl_)
823  {
824  if (nhLocal_.getParam("control_config", controlUpdateVector))
825  {
826  if (controlUpdateVector.size() != TWIST_SIZE)
827  {
828  ROS_ERROR_STREAM("Control configuration must be of size " << TWIST_SIZE << ". Provided config was of "
829  "size " << controlUpdateVector.size() << ". No control term will be used.");
830  useControl_ = false;
831  }
832  }
833  else
834  {
835  ROS_ERROR_STREAM("use_control is set to true, but control_config is missing. No control term will be used.");
836  useControl_ = false;
837  }
838 
839  if (nhLocal_.getParam("acceleration_limits", accelerationLimits))
840  {
841  if (accelerationLimits.size() != TWIST_SIZE)
842  {
843  ROS_ERROR_STREAM("Acceleration configuration must be of size " << TWIST_SIZE << ". Provided config was of "
844  "size " << accelerationLimits.size() << ". No control term will be used.");
845  useControl_ = false;
846  }
847  }
848  else
849  {
850  ROS_WARN_STREAM("use_control is set to true, but acceleration_limits is missing. Will use default values.");
851  }
852 
853  if (nhLocal_.getParam("acceleration_gains", accelerationGains))
854  {
855  const int size = accelerationGains.size();
856  if (size != TWIST_SIZE)
857  {
858  ROS_ERROR_STREAM("Acceleration gain configuration must be of size " << TWIST_SIZE <<
859  ". Provided config was of size " << size << ". All gains will be assumed to be 1.");
860  std::fill_n(accelerationGains.begin(), std::min(size, TWIST_SIZE), 1.0);
861  accelerationGains.resize(TWIST_SIZE, 1.0);
862  }
863  }
864 
865  if (nhLocal_.getParam("deceleration_limits", decelerationLimits))
866  {
867  if (decelerationLimits.size() != TWIST_SIZE)
868  {
869  ROS_ERROR_STREAM("Deceleration configuration must be of size " << TWIST_SIZE <<
870  ". Provided config was of size " << decelerationLimits.size() << ". No control term will be used.");
871  useControl_ = false;
872  }
873  }
874  else
875  {
876  ROS_INFO_STREAM("use_control is set to true, but no deceleration_limits specified. Will use acceleration "
877  "limits.");
878  decelerationLimits = accelerationLimits;
879  }
880 
881  if (nhLocal_.getParam("deceleration_gains", decelerationGains))
882  {
883  const int size = decelerationGains.size();
884  if (size != TWIST_SIZE)
885  {
886  ROS_ERROR_STREAM("Deceleration gain configuration must be of size " << TWIST_SIZE <<
887  ". Provided config was of size " << size << ". All gains will be assumed to be 1.");
888  std::fill_n(decelerationGains.begin(), std::min(size, TWIST_SIZE), 1.0);
889  decelerationGains.resize(TWIST_SIZE, 1.0);
890  }
891  }
892  else
893  {
894  ROS_INFO_STREAM("use_control is set to true, but no deceleration_gains specified. Will use acceleration "
895  "gains.");
896  decelerationGains = accelerationGains;
897  }
898  }
899 
900  bool dynamicProcessNoiseCovariance = false;
901  nhLocal_.param("dynamic_process_noise_covariance", dynamicProcessNoiseCovariance, false);
902  filter_.setUseDynamicProcessNoiseCovariance(dynamicProcessNoiseCovariance);
903 
904  std::vector<double> initialState(STATE_SIZE, 0.0);
905  if (nhLocal_.getParam("initial_state", initialState))
906  {
907  if (initialState.size() != STATE_SIZE)
908  {
909  ROS_ERROR_STREAM("Initial state must be of size " << STATE_SIZE << ". Provided config was of size " <<
910  initialState.size() << ". The initial state will be ignored.");
911  }
912  else
913  {
914  Eigen::Map<Eigen::VectorXd> eigenState(initialState.data(), initialState.size());
915  filter_.setState(eigenState);
916  }
917  }
918 
919  // Check if the filter should start or not
920  nhLocal_.param("disabled_at_startup", disabledAtStartup_, false);
922 
923 
924  // Debugging writes to file
925  RF_DEBUG("tf_prefix is " << tfPrefix <<
926  "\nmap_frame is " << mapFrameId_ <<
927  "\nodom_frame is " << odomFrameId_ <<
928  "\nbase_link_frame is " << baseLinkFrameId_ <<
929  "\base_link_frame_output is " << baseLinkOutputFrameId_ <<
930  "\nworld_frame is " << worldFrameId_ <<
931  "\ntransform_time_offset is " << tfTimeOffset_.toSec() <<
932  "\ntransform_timeout is " << tfTimeout_.toSec() <<
933  "\nfrequency is " << frequency_ <<
934  "\nsensor_timeout is " << filter_.getSensorTimeout() <<
935  "\ntwo_d_mode is " << (twoDMode_ ? "true" : "false") <<
936  "\nsmooth_lagged_data is " << (smoothLaggedData_ ? "true" : "false") <<
937  "\nhistory_length is " << historyLength_ <<
938  "\nuse_control is " << (useControl_ ? "true" : "false") <<
939  "\nstamped_control is " << (stampedControl ? "true" : "false") <<
940  "\ncontrol_config is " << controlUpdateVector <<
941  "\ncontrol_timeout is " << controlTimeout <<
942  "\nacceleration_limits are " << accelerationLimits <<
943  "\nacceleration_gains are " << accelerationGains <<
944  "\ndeceleration_limits are " << decelerationLimits <<
945  "\ndeceleration_gains are " << decelerationGains <<
946  "\ninitial state is " << filter_.getState() <<
947  "\ndynamic_process_noise_covariance is " << (dynamicProcessNoiseCovariance ? "true" : "false") <<
948  "\nprint_diagnostics is " << (printDiagnostics_ ? "true" : "false") << "\n");
949 
950  // Create a subscriber for manually setting/resetting pose
951  setPoseSub_ = nh_.subscribe("set_pose",
952  1,
954  this, ros::TransportHints().tcpNoDelay(false));
955 
956  // Create a service for manually setting/resetting pose
958 
959  // Create a service for manually enabling the filter
961 
962  // Create a service for toggling processing new measurements while still publishing
965 
966  // Init the last measurement time so we don't get a huge initial delta
967  filter_.setLastMeasurementTime(ros::Time::now().toSec());
968 
969  // Now pull in each topic to which we want to subscribe.
970  // Start with odom.
971  size_t topicInd = 0;
972  bool moreParams = false;
973  do
974  {
975  // Build the string in the form of "odomX", where X is the odom topic number,
976  // then check if we have any parameters with that value. Users need to make
977  // sure they don't have gaps in their configs (e.g., odom0 and then odom2)
978  std::stringstream ss;
979  ss << "odom" << topicInd++;
980  std::string odomTopicName = ss.str();
981  moreParams = nhLocal_.hasParam(odomTopicName);
982 
983  if (moreParams)
984  {
985  // Determine if we want to integrate this sensor differentially
986  bool differential;
987  nhLocal_.param(odomTopicName + std::string("_differential"), differential, false);
988 
989  // Determine if we want to integrate this sensor relatively
990  bool relative;
991  nhLocal_.param(odomTopicName + std::string("_relative"), relative, false);
992 
993  if (relative && differential)
994  {
995  ROS_WARN_STREAM("Both " << odomTopicName << "_differential" << " and " << odomTopicName <<
996  "_relative were set to true. Using differential mode.");
997 
998  relative = false;
999  }
1000 
1001  std::string odomTopic;
1002  nhLocal_.getParam(odomTopicName, odomTopic);
1003 
1004  // Check for pose rejection threshold
1005  double poseMahalanobisThresh;
1006  nhLocal_.param(odomTopicName + std::string("_pose_rejection_threshold"),
1007  poseMahalanobisThresh,
1008  std::numeric_limits<double>::max());
1009 
1010  // Check for twist rejection threshold
1011  double twistMahalanobisThresh;
1012  nhLocal_.param(odomTopicName + std::string("_twist_rejection_threshold"),
1013  twistMahalanobisThresh,
1014  std::numeric_limits<double>::max());
1015 
1016  // Now pull in its boolean update vector configuration. Create separate vectors for pose
1017  // and twist data, and then zero out the opposite values in each vector (no pose data in
1018  // the twist update vector and vice-versa).
1019  std::vector<int> updateVec = loadUpdateConfig(odomTopicName);
1020  std::vector<int> poseUpdateVec = updateVec;
1021  std::fill(poseUpdateVec.begin() + POSITION_V_OFFSET, poseUpdateVec.begin() + POSITION_V_OFFSET + TWIST_SIZE, 0);
1022  std::vector<int> twistUpdateVec = updateVec;
1023  std::fill(twistUpdateVec.begin() + POSITION_OFFSET, twistUpdateVec.begin() + POSITION_OFFSET + POSE_SIZE, 0);
1024 
1025  int poseUpdateSum = std::accumulate(poseUpdateVec.begin(), poseUpdateVec.end(), 0);
1026  int twistUpdateSum = std::accumulate(twistUpdateVec.begin(), twistUpdateVec.end(), 0);
1027  int odomQueueSize = 1;
1028  nhLocal_.param(odomTopicName + "_queue_size", odomQueueSize, 1);
1029 
1030  const CallbackData poseCallbackData(odomTopicName + "_pose", poseUpdateVec, poseUpdateSum, differential,
1031  relative, poseMahalanobisThresh);
1032  const CallbackData twistCallbackData(odomTopicName + "_twist", twistUpdateVec, twistUpdateSum, false, false,
1033  twistMahalanobisThresh);
1034 
1035  bool nodelayOdom = false;
1036  nhLocal_.param(odomTopicName + "_nodelay", nodelayOdom, false);
1037 
1038  // Store the odometry topic subscribers so they don't go out of scope.
1039  if (poseUpdateSum + twistUpdateSum > 0)
1040  {
1041  topicSubs_.push_back(
1042  nh_.subscribe<nav_msgs::Odometry>(odomTopic, odomQueueSize,
1043  boost::bind(&RosFilter::odometryCallback, this, _1, odomTopicName, poseCallbackData, twistCallbackData),
1044  ros::VoidPtr(), ros::TransportHints().tcpNoDelay(nodelayOdom)));
1045  }
1046  else
1047  {
1048  std::stringstream stream;
1049  stream << odomTopic << " is listed as an input topic, but all update variables are false";
1050 
1051  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1052  odomTopic + "_configuration",
1053  stream.str(),
1054  true);
1055  }
1056 
1057  if (poseUpdateSum > 0)
1058  {
1059  if (differential)
1060  {
1061  twistVarCounts[StateMemberVx] += poseUpdateVec[StateMemberX];
1062  twistVarCounts[StateMemberVy] += poseUpdateVec[StateMemberY];
1063  twistVarCounts[StateMemberVz] += poseUpdateVec[StateMemberZ];
1064  twistVarCounts[StateMemberVroll] += poseUpdateVec[StateMemberRoll];
1065  twistVarCounts[StateMemberVpitch] += poseUpdateVec[StateMemberPitch];
1066  twistVarCounts[StateMemberVyaw] += poseUpdateVec[StateMemberYaw];
1067  }
1068  else
1069  {
1070  absPoseVarCounts[StateMemberX] += poseUpdateVec[StateMemberX];
1071  absPoseVarCounts[StateMemberY] += poseUpdateVec[StateMemberY];
1072  absPoseVarCounts[StateMemberZ] += poseUpdateVec[StateMemberZ];
1073  absPoseVarCounts[StateMemberRoll] += poseUpdateVec[StateMemberRoll];
1074  absPoseVarCounts[StateMemberPitch] += poseUpdateVec[StateMemberPitch];
1075  absPoseVarCounts[StateMemberYaw] += poseUpdateVec[StateMemberYaw];
1076  }
1077  }
1078 
1079  if (twistUpdateSum > 0)
1080  {
1081  twistVarCounts[StateMemberVx] += twistUpdateVec[StateMemberVx];
1082  twistVarCounts[StateMemberVy] += twistUpdateVec[StateMemberVx];
1083  twistVarCounts[StateMemberVz] += twistUpdateVec[StateMemberVz];
1084  twistVarCounts[StateMemberVroll] += twistUpdateVec[StateMemberVroll];
1085  twistVarCounts[StateMemberVpitch] += twistUpdateVec[StateMemberVpitch];
1086  twistVarCounts[StateMemberVyaw] += twistUpdateVec[StateMemberVyaw];
1087  }
1088 
1089  RF_DEBUG("Subscribed to " << odomTopic << " (" << odomTopicName << ")\n\t" <<
1090  odomTopicName << "_differential is " << (differential ? "true" : "false") << "\n\t" <<
1091  odomTopicName << "_pose_rejection_threshold is " << poseMahalanobisThresh << "\n\t" <<
1092  odomTopicName << "_twist_rejection_threshold is " << twistMahalanobisThresh << "\n\t" <<
1093  odomTopicName << "_queue_size is " << odomQueueSize << "\n\t" <<
1094  odomTopicName << " pose update vector is " << poseUpdateVec << "\t"<<
1095  odomTopicName << " twist update vector is " << twistUpdateVec);
1096  }
1097  }
1098  while (moreParams);
1099 
1100  // Repeat for pose
1101  topicInd = 0;
1102  moreParams = false;
1103  do
1104  {
1105  std::stringstream ss;
1106  ss << "pose" << topicInd++;
1107  std::string poseTopicName = ss.str();
1108  moreParams = nhLocal_.hasParam(poseTopicName);
1109 
1110  if (moreParams)
1111  {
1112  bool differential;
1113  nhLocal_.param(poseTopicName + std::string("_differential"), differential, false);
1114 
1115  // Determine if we want to integrate this sensor relatively
1116  bool relative;
1117  nhLocal_.param(poseTopicName + std::string("_relative"), relative, false);
1118 
1119  if (relative && differential)
1120  {
1121  ROS_WARN_STREAM("Both " << poseTopicName << "_differential" << " and " << poseTopicName <<
1122  "_relative were set to true. Using differential mode.");
1123 
1124  relative = false;
1125  }
1126 
1127  std::string poseTopic;
1128  nhLocal_.getParam(poseTopicName, poseTopic);
1129 
1130  // Check for pose rejection threshold
1131  double poseMahalanobisThresh;
1132  nhLocal_.param(poseTopicName + std::string("_rejection_threshold"),
1133  poseMahalanobisThresh,
1134  std::numeric_limits<double>::max());
1135 
1136  int poseQueueSize = 1;
1137  nhLocal_.param(poseTopicName + "_queue_size", poseQueueSize, 1);
1138 
1139  bool nodelayPose = false;
1140  nhLocal_.param(poseTopicName + "_nodelay", nodelayPose, false);
1141 
1142  // Pull in the sensor's config, zero out values that are invalid for the pose type
1143  std::vector<int> poseUpdateVec = loadUpdateConfig(poseTopicName);
1144  std::fill(poseUpdateVec.begin() + POSITION_V_OFFSET,
1145  poseUpdateVec.begin() + POSITION_V_OFFSET + TWIST_SIZE,
1146  0);
1147  std::fill(poseUpdateVec.begin() + POSITION_A_OFFSET,
1148  poseUpdateVec.begin() + POSITION_A_OFFSET + ACCELERATION_SIZE,
1149  0);
1150 
1151  int poseUpdateSum = std::accumulate(poseUpdateVec.begin(), poseUpdateVec.end(), 0);
1152 
1153  if (poseUpdateSum > 0)
1154  {
1155  const CallbackData callbackData(poseTopicName, poseUpdateVec, poseUpdateSum, differential, relative,
1156  poseMahalanobisThresh);
1157 
1158  topicSubs_.push_back(
1159  nh_.subscribe<geometry_msgs::PoseWithCovarianceStamped>(poseTopic, poseQueueSize,
1160  boost::bind(&RosFilter::poseCallback, this, _1, callbackData, worldFrameId_, false),
1161  ros::VoidPtr(), ros::TransportHints().tcpNoDelay(nodelayPose)));
1162 
1163  if (differential)
1164  {
1165  twistVarCounts[StateMemberVx] += poseUpdateVec[StateMemberX];
1166  twistVarCounts[StateMemberVy] += poseUpdateVec[StateMemberY];
1167  twistVarCounts[StateMemberVz] += poseUpdateVec[StateMemberZ];
1168  twistVarCounts[StateMemberVroll] += poseUpdateVec[StateMemberRoll];
1169  twistVarCounts[StateMemberVpitch] += poseUpdateVec[StateMemberPitch];
1170  twistVarCounts[StateMemberVyaw] += poseUpdateVec[StateMemberYaw];
1171  }
1172  else
1173  {
1174  absPoseVarCounts[StateMemberX] += poseUpdateVec[StateMemberX];
1175  absPoseVarCounts[StateMemberY] += poseUpdateVec[StateMemberY];
1176  absPoseVarCounts[StateMemberZ] += poseUpdateVec[StateMemberZ];
1177  absPoseVarCounts[StateMemberRoll] += poseUpdateVec[StateMemberRoll];
1178  absPoseVarCounts[StateMemberPitch] += poseUpdateVec[StateMemberPitch];
1179  absPoseVarCounts[StateMemberYaw] += poseUpdateVec[StateMemberYaw];
1180  }
1181  }
1182  else
1183  {
1184  ROS_WARN_STREAM("Warning: " << poseTopic << " is listed as an input topic, "
1185  "but all pose update variables are false");
1186  }
1187 
1188  RF_DEBUG("Subscribed to " << poseTopic << " (" << poseTopicName << ")\n\t" <<
1189  poseTopicName << "_differential is " << (differential ? "true" : "false") << "\n\t" <<
1190  poseTopicName << "_rejection_threshold is " << poseMahalanobisThresh << "\n\t" <<
1191  poseTopicName << "_queue_size is " << poseQueueSize << "\n\t" <<
1192  poseTopicName << " update vector is " << poseUpdateVec);
1193  }
1194  }
1195  while (moreParams);
1196 
1197  // Repeat for twist
1198  topicInd = 0;
1199  moreParams = false;
1200  do
1201  {
1202  std::stringstream ss;
1203  ss << "twist" << topicInd++;
1204  std::string twistTopicName = ss.str();
1205  moreParams = nhLocal_.hasParam(twistTopicName);
1206 
1207  if (moreParams)
1208  {
1209  std::string twistTopic;
1210  nhLocal_.getParam(twistTopicName, twistTopic);
1211 
1212  // Check for twist rejection threshold
1213  double twistMahalanobisThresh;
1214  nhLocal_.param(twistTopicName + std::string("_rejection_threshold"),
1215  twistMahalanobisThresh,
1216  std::numeric_limits<double>::max());
1217 
1218  int twistQueueSize = 1;
1219  nhLocal_.param(twistTopicName + "_queue_size", twistQueueSize, 1);
1220 
1221  bool nodelayTwist = false;
1222  nhLocal_.param(twistTopicName + "_nodelay", nodelayTwist, false);
1223 
1224  // Pull in the sensor's config, zero out values that are invalid for the twist type
1225  std::vector<int> twistUpdateVec = loadUpdateConfig(twistTopicName);
1226  std::fill(twistUpdateVec.begin() + POSITION_OFFSET, twistUpdateVec.begin() + POSITION_OFFSET + POSE_SIZE, 0);
1227 
1228  int twistUpdateSum = std::accumulate(twistUpdateVec.begin(), twistUpdateVec.end(), 0);
1229 
1230  if (twistUpdateSum > 0)
1231  {
1232  const CallbackData callbackData(twistTopicName, twistUpdateVec, twistUpdateSum, false, false,
1233  twistMahalanobisThresh);
1234 
1235  topicSubs_.push_back(
1236  nh_.subscribe<geometry_msgs::TwistWithCovarianceStamped>(twistTopic, twistQueueSize,
1237  boost::bind(&RosFilter<T>::twistCallback, this, _1, callbackData, baseLinkFrameId_),
1238  ros::VoidPtr(), ros::TransportHints().tcpNoDelay(nodelayTwist)));
1239 
1240  twistVarCounts[StateMemberVx] += twistUpdateVec[StateMemberVx];
1241  twistVarCounts[StateMemberVy] += twistUpdateVec[StateMemberVy];
1242  twistVarCounts[StateMemberVz] += twistUpdateVec[StateMemberVz];
1243  twistVarCounts[StateMemberVroll] += twistUpdateVec[StateMemberVroll];
1244  twistVarCounts[StateMemberVpitch] += twistUpdateVec[StateMemberVpitch];
1245  twistVarCounts[StateMemberVyaw] += twistUpdateVec[StateMemberVyaw];
1246  }
1247  else
1248  {
1249  ROS_WARN_STREAM("Warning: " << twistTopic << " is listed as an input topic, "
1250  "but all twist update variables are false");
1251  }
1252 
1253  RF_DEBUG("Subscribed to " << twistTopic << " (" << twistTopicName << ")\n\t" <<
1254  twistTopicName << "_rejection_threshold is " << twistMahalanobisThresh << "\n\t" <<
1255  twistTopicName << "_queue_size is " << twistQueueSize << "\n\t" <<
1256  twistTopicName << " update vector is " << twistUpdateVec);
1257  }
1258  }
1259  while (moreParams);
1260 
1261  // Repeat for IMU
1262  topicInd = 0;
1263  moreParams = false;
1264  do
1265  {
1266  std::stringstream ss;
1267  ss << "imu" << topicInd++;
1268  std::string imuTopicName = ss.str();
1269  moreParams = nhLocal_.hasParam(imuTopicName);
1270 
1271  if (moreParams)
1272  {
1273  bool differential;
1274  nhLocal_.param(imuTopicName + std::string("_differential"), differential, false);
1275 
1276  // Determine if we want to integrate this sensor relatively
1277  bool relative;
1278  nhLocal_.param(imuTopicName + std::string("_relative"), relative, false);
1279 
1280  if (relative && differential)
1281  {
1282  ROS_WARN_STREAM("Both " << imuTopicName << "_differential" << " and " << imuTopicName <<
1283  "_relative were set to true. Using differential mode.");
1284 
1285  relative = false;
1286  }
1287 
1288  std::string imuTopic;
1289  nhLocal_.getParam(imuTopicName, imuTopic);
1290 
1291  // Check for pose rejection threshold
1292  double poseMahalanobisThresh;
1293  nhLocal_.param(imuTopicName + std::string("_pose_rejection_threshold"),
1294  poseMahalanobisThresh,
1295  std::numeric_limits<double>::max());
1296 
1297  // Check for angular velocity rejection threshold
1298  double twistMahalanobisThresh;
1299  std::string imuTwistRejectionName =
1300  imuTopicName + std::string("_twist_rejection_threshold");
1301  nhLocal_.param(imuTwistRejectionName, twistMahalanobisThresh, std::numeric_limits<double>::max());
1302 
1303  // Check for acceleration rejection threshold
1304  double accelMahalanobisThresh;
1305  nhLocal_.param(imuTopicName + std::string("_linear_acceleration_rejection_threshold"),
1306  accelMahalanobisThresh,
1307  std::numeric_limits<double>::max());
1308 
1309  bool removeGravAcc = false;
1310  nhLocal_.param(imuTopicName + "_remove_gravitational_acceleration", removeGravAcc, false);
1311  removeGravitationalAcc_[imuTopicName + "_acceleration"] = removeGravAcc;
1312 
1313  // Now pull in its boolean update vector configuration and differential
1314  // update configuration (as this contains pose information)
1315  std::vector<int> updateVec = loadUpdateConfig(imuTopicName);
1316 
1317  std::vector<int> poseUpdateVec = updateVec;
1318  std::fill(poseUpdateVec.begin() + POSITION_V_OFFSET,
1319  poseUpdateVec.begin() + POSITION_V_OFFSET + TWIST_SIZE,
1320  0);
1321  std::fill(poseUpdateVec.begin() + POSITION_A_OFFSET,
1322  poseUpdateVec.begin() + POSITION_A_OFFSET + ACCELERATION_SIZE,
1323  0);
1324 
1325  std::vector<int> twistUpdateVec = updateVec;
1326  std::fill(twistUpdateVec.begin() + POSITION_OFFSET,
1327  twistUpdateVec.begin() + POSITION_OFFSET + POSE_SIZE,
1328  0);
1329  std::fill(twistUpdateVec.begin() + POSITION_A_OFFSET,
1330  twistUpdateVec.begin() + POSITION_A_OFFSET + ACCELERATION_SIZE,
1331  0);
1332 
1333  std::vector<int> accelUpdateVec = updateVec;
1334  std::fill(accelUpdateVec.begin() + POSITION_OFFSET,
1335  accelUpdateVec.begin() + POSITION_OFFSET + POSE_SIZE,
1336  0);
1337  std::fill(accelUpdateVec.begin() + POSITION_V_OFFSET,
1338  accelUpdateVec.begin() + POSITION_V_OFFSET + TWIST_SIZE,
1339  0);
1340 
1341  int poseUpdateSum = std::accumulate(poseUpdateVec.begin(), poseUpdateVec.end(), 0);
1342  int twistUpdateSum = std::accumulate(twistUpdateVec.begin(), twistUpdateVec.end(), 0);
1343  int accelUpdateSum = std::accumulate(accelUpdateVec.begin(), accelUpdateVec.end(), 0);
1344 
1345  // Check if we're using control input for any of the acceleration variables; turn off if so
1346  if (static_cast<bool>(controlUpdateVector[ControlMemberVx]) && static_cast<bool>(accelUpdateVec[StateMemberAx]))
1347  {
1348  ROS_WARN_STREAM("X acceleration is being measured from IMU; X velocity control input is disabled");
1349  controlUpdateVector[ControlMemberVx] = 0;
1350  }
1351  if (static_cast<bool>(controlUpdateVector[ControlMemberVy]) && static_cast<bool>(accelUpdateVec[StateMemberAy]))
1352  {
1353  ROS_WARN_STREAM("Y acceleration is being measured from IMU; Y velocity control input is disabled");
1354  controlUpdateVector[ControlMemberVy] = 0;
1355  }
1356  if (static_cast<bool>(controlUpdateVector[ControlMemberVz]) && static_cast<bool>(accelUpdateVec[StateMemberAz]))
1357  {
1358  ROS_WARN_STREAM("Z acceleration is being measured from IMU; Z velocity control input is disabled");
1359  controlUpdateVector[ControlMemberVz] = 0;
1360  }
1361 
1362  int imuQueueSize = 1;
1363  nhLocal_.param(imuTopicName + "_queue_size", imuQueueSize, 1);
1364 
1365  bool nodelayImu = false;
1366  nhLocal_.param(imuTopicName + "_nodelay", nodelayImu, false);
1367 
1368  if (poseUpdateSum + twistUpdateSum + accelUpdateSum > 0)
1369  {
1370  const CallbackData poseCallbackData(imuTopicName + "_pose", poseUpdateVec, poseUpdateSum, differential,
1371  relative, poseMahalanobisThresh);
1372  const CallbackData twistCallbackData(imuTopicName + "_twist", twistUpdateVec, twistUpdateSum, differential,
1373  relative, twistMahalanobisThresh);
1374  const CallbackData accelCallbackData(imuTopicName + "_acceleration", accelUpdateVec, accelUpdateSum,
1375  differential, relative, accelMahalanobisThresh);
1376 
1377  topicSubs_.push_back(
1378  nh_.subscribe<sensor_msgs::Imu>(imuTopic, imuQueueSize,
1379  boost::bind(&RosFilter<T>::imuCallback, this, _1, imuTopicName, poseCallbackData, twistCallbackData,
1380  accelCallbackData), ros::VoidPtr(), ros::TransportHints().tcpNoDelay(nodelayImu)));
1381  }
1382  else
1383  {
1384  ROS_WARN_STREAM("Warning: " << imuTopic << " is listed as an input topic, "
1385  "but all its update variables are false");
1386  }
1387 
1388  if (poseUpdateSum > 0)
1389  {
1390  if (differential)
1391  {
1392  twistVarCounts[StateMemberVroll] += poseUpdateVec[StateMemberRoll];
1393  twistVarCounts[StateMemberVpitch] += poseUpdateVec[StateMemberPitch];
1394  twistVarCounts[StateMemberVyaw] += poseUpdateVec[StateMemberYaw];
1395  }
1396  else
1397  {
1398  absPoseVarCounts[StateMemberRoll] += poseUpdateVec[StateMemberRoll];
1399  absPoseVarCounts[StateMemberPitch] += poseUpdateVec[StateMemberPitch];
1400  absPoseVarCounts[StateMemberYaw] += poseUpdateVec[StateMemberYaw];
1401  }
1402  }
1403 
1404  if (twistUpdateSum > 0)
1405  {
1406  twistVarCounts[StateMemberVroll] += twistUpdateVec[StateMemberVroll];
1407  twistVarCounts[StateMemberVpitch] += twistUpdateVec[StateMemberVpitch];
1408  twistVarCounts[StateMemberVyaw] += twistUpdateVec[StateMemberVyaw];
1409  }
1410 
1411  RF_DEBUG("Subscribed to " << imuTopic << " (" << imuTopicName << ")\n\t" <<
1412  imuTopicName << "_differential is " << (differential ? "true" : "false") << "\n\t" <<
1413  imuTopicName << "_pose_rejection_threshold is " << poseMahalanobisThresh << "\n\t" <<
1414  imuTopicName << "_twist_rejection_threshold is " << twistMahalanobisThresh << "\n\t" <<
1415  imuTopicName << "_linear_acceleration_rejection_threshold is " << accelMahalanobisThresh << "\n\t" <<
1416  imuTopicName << "_remove_gravitational_acceleration is " <<
1417  (removeGravAcc ? "true" : "false") << "\n\t" <<
1418  imuTopicName << "_queue_size is " << imuQueueSize << "\n\t" <<
1419  imuTopicName << " pose update vector is " << poseUpdateVec << "\t"<<
1420  imuTopicName << " twist update vector is " << twistUpdateVec << "\t" <<
1421  imuTopicName << " acceleration update vector is " << accelUpdateVec);
1422  }
1423  }
1424  while (moreParams);
1425 
1426  // Now that we've checked if IMU linear acceleration is being used, we can determine our final control parameters
1427  if (useControl_ && std::accumulate(controlUpdateVector.begin(), controlUpdateVector.end(), 0) == 0)
1428  {
1429  ROS_ERROR_STREAM("use_control is set to true, but control_config has only false values. No control term "
1430  "will be used.");
1431  useControl_ = false;
1432  }
1433 
1434  // If we're using control, set the parameters and create the necessary subscribers
1435  if (useControl_)
1436  {
1437  latestControl_.resize(TWIST_SIZE);
1438  latestControl_.setZero();
1439 
1440  filter_.setControlParams(controlUpdateVector, controlTimeout, accelerationLimits, accelerationGains,
1441  decelerationLimits, decelerationGains);
1442 
1443  if (stampedControl)
1444  {
1445  controlSub_ = nh_.subscribe<geometry_msgs::TwistStamped>("cmd_vel", 1, &RosFilter<T>::controlCallback, this);
1446  }
1447  else
1448  {
1449  controlSub_ = nh_.subscribe<geometry_msgs::Twist>("cmd_vel", 1, &RosFilter<T>::controlCallback, this);
1450  }
1451  }
1452 
1453  /* Warn users about:
1454  * 1. Multiple non-differential input sources
1455  * 2. No absolute *or* velocity measurements for pose variables
1456  */
1457  if (printDiagnostics_)
1458  {
1459  for (int stateVar = StateMemberX; stateVar <= StateMemberYaw; ++stateVar)
1460  {
1461  if (absPoseVarCounts[static_cast<StateMembers>(stateVar)] > 1)
1462  {
1463  std::stringstream stream;
1464  stream << absPoseVarCounts[static_cast<StateMembers>(stateVar - POSITION_OFFSET)] <<
1465  " absolute pose inputs detected for " << stateVariableNames_[stateVar] <<
1466  ". This may result in oscillations. Please ensure that your variances for each "
1467  "measured variable are set appropriately.";
1468 
1469  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1470  stateVariableNames_[stateVar] + "_configuration",
1471  stream.str(),
1472  true);
1473  }
1474  else if (absPoseVarCounts[static_cast<StateMembers>(stateVar)] == 0)
1475  {
1476  if ((static_cast<StateMembers>(stateVar) == StateMemberX &&
1477  twistVarCounts[static_cast<StateMembers>(StateMemberVx)] == 0) ||
1478  (static_cast<StateMembers>(stateVar) == StateMemberY &&
1479  twistVarCounts[static_cast<StateMembers>(StateMemberVy)] == 0) ||
1480  (static_cast<StateMembers>(stateVar) == StateMemberZ &&
1481  twistVarCounts[static_cast<StateMembers>(StateMemberVz)] == 0 &&
1482  twoDMode_ == false) ||
1483  (static_cast<StateMembers>(stateVar) == StateMemberRoll &&
1484  twistVarCounts[static_cast<StateMembers>(StateMemberVroll)] == 0 &&
1485  twoDMode_ == false) ||
1486  (static_cast<StateMembers>(stateVar) == StateMemberPitch &&
1487  twistVarCounts[static_cast<StateMembers>(StateMemberVpitch)] == 0 &&
1488  twoDMode_ == false) ||
1489  (static_cast<StateMembers>(stateVar) == StateMemberYaw &&
1490  twistVarCounts[static_cast<StateMembers>(StateMemberVyaw)] == 0))
1491  {
1492  std::stringstream stream;
1493  stream << "Neither " << stateVariableNames_[stateVar] << " nor its "
1494  "velocity is being measured. This will result in unbounded "
1495  "error growth and erratic filter behavior.";
1496 
1497  addDiagnostic(diagnostic_msgs::DiagnosticStatus::ERROR,
1498  stateVariableNames_[stateVar] + "_configuration",
1499  stream.str(),
1500  true);
1501  }
1502  }
1503  }
1504  }
1505 
1506  // Load up the process noise covariance (from the launch file/parameter server)
1507  Eigen::MatrixXd processNoiseCovariance(STATE_SIZE, STATE_SIZE);
1508  processNoiseCovariance.setZero();
1509  XmlRpc::XmlRpcValue processNoiseCovarConfig;
1510 
1511  if (nhLocal_.hasParam("process_noise_covariance"))
1512  {
1513  try
1514  {
1515  nhLocal_.getParam("process_noise_covariance", processNoiseCovarConfig);
1516 
1517  ROS_ASSERT(processNoiseCovarConfig.getType() == XmlRpc::XmlRpcValue::TypeArray);
1518 
1519  int matSize = processNoiseCovariance.rows();
1520 
1521  for (int i = 0; i < matSize; i++)
1522  {
1523  for (int j = 0; j < matSize; j++)
1524  {
1525  try
1526  {
1527  // These matrices can cause problems if all the types
1528  // aren't specified with decimal points. Handle that
1529  // using string streams.
1530  std::ostringstream ostr;
1531  ostr << processNoiseCovarConfig[matSize * i + j];
1532  std::istringstream istr(ostr.str());
1533  istr >> processNoiseCovariance(i, j);
1534  }
1535  catch(XmlRpc::XmlRpcException &e)
1536  {
1537  throw e;
1538  }
1539  catch(...)
1540  {
1541  throw;
1542  }
1543  }
1544  }
1545 
1546  RF_DEBUG("Process noise covariance is:\n" << processNoiseCovariance << "\n");
1547  }
1548  catch (XmlRpc::XmlRpcException &e)
1549  {
1550  ROS_ERROR_STREAM("ERROR reading sensor config: " <<
1551  e.getMessage() <<
1552  " for process_noise_covariance (type: " <<
1553  processNoiseCovarConfig.getType() << ")");
1554  }
1555 
1556  filter_.setProcessNoiseCovariance(processNoiseCovariance);
1557  }
1558 
1559  // Load up the process noise covariance (from the launch file/parameter server)
1560  Eigen::MatrixXd initialEstimateErrorCovariance(STATE_SIZE, STATE_SIZE);
1561  initialEstimateErrorCovariance.setZero();
1562  XmlRpc::XmlRpcValue estimateErrorCovarConfig;
1563 
1564  if (nhLocal_.hasParam("initial_estimate_covariance"))
1565  {
1566  try
1567  {
1568  nhLocal_.getParam("initial_estimate_covariance", estimateErrorCovarConfig);
1569 
1570  ROS_ASSERT(estimateErrorCovarConfig.getType() == XmlRpc::XmlRpcValue::TypeArray);
1571 
1572  int matSize = initialEstimateErrorCovariance.rows();
1573 
1574  for (int i = 0; i < matSize; i++)
1575  {
1576  for (int j = 0; j < matSize; j++)
1577  {
1578  try
1579  {
1580  // These matrices can cause problems if all the types
1581  // aren't specified with decimal points. Handle that
1582  // using string streams.
1583  std::ostringstream ostr;
1584  ostr << estimateErrorCovarConfig[matSize * i + j];
1585  std::istringstream istr(ostr.str());
1586  istr >> initialEstimateErrorCovariance(i, j);
1587  }
1588  catch(XmlRpc::XmlRpcException &e)
1589  {
1590  throw e;
1591  }
1592  catch(...)
1593  {
1594  throw;
1595  }
1596  }
1597  }
1598 
1599  RF_DEBUG("Initial estimate error covariance is:\n" << initialEstimateErrorCovariance << "\n");
1600  }
1601  catch (XmlRpc::XmlRpcException &e)
1602  {
1603  ROS_ERROR_STREAM("ERROR reading initial_estimate_covariance (type: " <<
1604  estimateErrorCovarConfig.getType() <<
1605  "): " <<
1606  e.getMessage());
1607  }
1608  catch(...)
1609  {
1611  "ERROR reading initial_estimate_covariance (type: " << estimateErrorCovarConfig.getType() << ")");
1612  }
1613 
1614  filter_.setEstimateErrorCovariance(initialEstimateErrorCovariance);
1615  }
1616  }
1617 
1618  template<typename T>
1619  void RosFilter<T>::odometryCallback(const nav_msgs::Odometry::ConstPtr &msg, const std::string &topicName,
1620  const CallbackData &poseCallbackData, const CallbackData &twistCallbackData)
1621  {
1622  // If we've just reset the filter, then we want to ignore any messages
1623  // that arrive with an older timestamp
1624  if (msg->header.stamp <= lastSetPoseTime_)
1625  {
1626  std::stringstream stream;
1627  stream << "The " << topicName << " message has a timestamp equal to or before the last filter reset, " <<
1628  "this message will be ignored. This may indicate an empty or bad timestamp. (message time: " <<
1629  msg->header.stamp.toSec() << ")";
1630  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1631  topicName + "_timestamp",
1632  stream.str(),
1633  false);
1634  RF_DEBUG("Received message that preceded the most recent pose reset. Ignoring...");
1635  return;
1636  }
1637 
1638  RF_DEBUG("------ RosFilter::odometryCallback (" << topicName << ") ------\n" << "Odometry message:\n" << *msg);
1639 
1640  if (poseCallbackData.updateSum_ > 0)
1641  {
1642  // Grab the pose portion of the message and pass it to the poseCallback
1643  geometry_msgs::PoseWithCovarianceStamped *posPtr = new geometry_msgs::PoseWithCovarianceStamped();
1644  posPtr->header = msg->header;
1645  posPtr->pose = msg->pose; // Entire pose object, also copies covariance
1646 
1647  geometry_msgs::PoseWithCovarianceStampedConstPtr pptr(posPtr);
1648  poseCallback(pptr, poseCallbackData, worldFrameId_, false);
1649  }
1650 
1651  if (twistCallbackData.updateSum_ > 0)
1652  {
1653  // Grab the twist portion of the message and pass it to the twistCallback
1654  geometry_msgs::TwistWithCovarianceStamped *twistPtr = new geometry_msgs::TwistWithCovarianceStamped();
1655  twistPtr->header = msg->header;
1656  twistPtr->header.frame_id = msg->child_frame_id;
1657  twistPtr->twist = msg->twist; // Entire twist object, also copies covariance
1658 
1659  geometry_msgs::TwistWithCovarianceStampedConstPtr tptr(twistPtr);
1660  twistCallback(tptr, twistCallbackData, baseLinkFrameId_);
1661  }
1662 
1663  RF_DEBUG("\n----- /RosFilter::odometryCallback (" << topicName << ") ------\n");
1664  }
1665 
1666  template<typename T>
1667  void RosFilter<T>::poseCallback(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg,
1668  const CallbackData &callbackData,
1669  const std::string &targetFrame,
1670  const bool imuData)
1671  {
1672  const std::string &topicName = callbackData.topicName_;
1673 
1674  // If we've just reset the filter, then we want to ignore any messages
1675  // that arrive with an older timestamp
1676  if (msg->header.stamp <= lastSetPoseTime_)
1677  {
1678  std::stringstream stream;
1679  stream << "The " << topicName << " message has a timestamp equal to or before the last filter reset, " <<
1680  "this message will be ignored. This may indicate an empty or bad timestamp. (message time: " <<
1681  msg->header.stamp.toSec() << ")";
1682  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1683  topicName + "_timestamp",
1684  stream.str(),
1685  false);
1686  return;
1687  }
1688 
1689  RF_DEBUG("------ RosFilter::poseCallback (" << topicName << ") ------\n" <<
1690  "Pose message:\n" << *msg);
1691 
1692  // Put the initial value in the lastMessagTimes_ for this variable if it's empty
1693  if (lastMessageTimes_.count(topicName) == 0)
1694  {
1695  lastMessageTimes_.insert(std::pair<std::string, ros::Time>(topicName, msg->header.stamp));
1696  }
1697 
1698  // Make sure this message is newer than the last one
1699  if (msg->header.stamp >= lastMessageTimes_[topicName])
1700  {
1701  RF_DEBUG("Update vector for " << topicName << " is:\n" << callbackData.updateVector_);
1702 
1703  Eigen::VectorXd measurement(STATE_SIZE);
1704  Eigen::MatrixXd measurementCovariance(STATE_SIZE, STATE_SIZE);
1705 
1706  measurement.setZero();
1707  measurementCovariance.setZero();
1708 
1709  // Make sure we're actually updating at least one of these variables
1710  std::vector<int> updateVectorCorrected = callbackData.updateVector_;
1711 
1712  // Prepare the pose data for inclusion in the filter
1713  if (preparePose(msg,
1714  topicName,
1715  targetFrame,
1716  callbackData.differential_,
1717  callbackData.relative_,
1718  imuData,
1719  updateVectorCorrected,
1720  measurement,
1721  measurementCovariance))
1722  {
1723  // Store the measurement. Add a "pose" suffix so we know what kind of measurement
1724  // we're dealing with when we debug the core filter logic.
1725  enqueueMeasurement(topicName,
1726  measurement,
1727  measurementCovariance,
1728  updateVectorCorrected,
1729  callbackData.rejectionThreshold_,
1730  msg->header.stamp);
1731 
1732  RF_DEBUG("Enqueued new measurement for " << topicName << "\n");
1733  }
1734  else
1735  {
1736  RF_DEBUG("Did *not* enqueue measurement for " << topicName << "\n");
1737  }
1738 
1739  lastMessageTimes_[topicName] = msg->header.stamp;
1740 
1741  RF_DEBUG("Last message time for " << topicName << " is now " <<
1742  lastMessageTimes_[topicName] << "\n");
1743  }
1744  else if (resetOnTimeJump_ && ros::Time::isSimTime())
1745  {
1746  reset();
1747  }
1748  else
1749  {
1750  std::stringstream stream;
1751  stream << "The " << topicName << " message has a timestamp before that of the previous message received," <<
1752  " this message will be ignored. This may indicate a bad timestamp. (message time: " <<
1753  msg->header.stamp.toSec() << ")";
1754  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
1755  topicName + "_timestamp",
1756  stream.str(),
1757  false);
1758 
1759  RF_DEBUG("Message is too old. Last message time for " << topicName << " is "
1760  << lastMessageTimes_[topicName] << ", current message time is "
1761  << msg->header.stamp << ".\n");
1762  }
1763 
1764  RF_DEBUG("\n----- /RosFilter::poseCallback (" << topicName << ") ------\n");
1765  }
1766 
1767  template<typename T>
1769  {
1770  ROS_INFO("Waiting for valid clock time...");
1772  ROS_INFO("Valid clock time received. Starting node.");
1773 
1774  loadParams();
1775 
1776  if (printDiagnostics_)
1777  {
1778  diagnosticUpdater_.add("Filter diagnostic updater", this, &RosFilter<T>::aggregateDiagnostics);
1779  }
1780 
1781  // Set up the frequency diagnostic
1782  double minFrequency = frequency_ - 2;
1783  double maxFrequency = frequency_ + 2;
1784  diagnostic_updater::HeaderlessTopicDiagnostic freqDiag("odometry/filtered",
1787  &maxFrequency,
1788  0.1, 10));
1789 
1790  // We may need to broadcast a different transform than
1791  // the one we've already calculated.
1792  tf2::Transform mapOdomTrans;
1793  tf2::Transform baseLinkOdomTrans;
1794  geometry_msgs::TransformStamped mapOdomTransMsg;
1795  ros::Time curTime;
1796  ros::Time lastDiagTime = ros::Time::now();
1797 
1798  // Clear out the transforms
1800  mapOdomTransMsg.transform = tf2::toMsg(tf2::Transform::getIdentity());
1801 
1802  // Publisher
1803  ros::Publisher positionPub = nh_.advertise<nav_msgs::Odometry>("odometry/filtered", 20);
1804  tf2_ros::TransformBroadcaster worldTransformBroadcaster;
1805 
1806  // Optional acceleration publisher
1807  ros::Publisher accelPub;
1809  {
1810  accelPub = nh_.advertise<geometry_msgs::AccelWithCovarianceStamped>("accel/filtered", 20);
1811  }
1812 
1813  const ros::Duration loop_cycle_time(1.0 / frequency_);
1814  ros::Time loop_end_time = ros::Time::now();
1815 
1816  // Wait for the filter to be enabled
1817  while (!enabled_ && ros::ok())
1818  {
1820  ":] This filter is disabled. To enable it call the service " << ros::this_node::getName() << "/enable");
1821  ros::spinOnce();
1822  if (enabled_)
1823  {
1824  break;
1825  }
1826  }
1827 
1828  while (ros::ok())
1829  {
1830  // The spin will call all the available callbacks and enqueue
1831  // their received measurements
1832  ros::spinOnce();
1833  curTime = ros::Time::now();
1834 
1835  if (toggledOn_)
1836  {
1837  // Now we'll integrate any measurements we've received if requested
1838  integrateMeasurements(curTime);
1839  }
1840  else
1841  {
1842  // clear out measurements since we're not currently processing new entries
1844 
1845  // Reset last measurement time so we don't get a large time delta on toggle on
1846  if (filter_.getInitializedStatus())
1847  {
1848  filter_.setLastMeasurementTime(ros::Time::now().toSec());
1849  }
1850  }
1851 
1852  // Get latest state and publish it
1853  nav_msgs::Odometry filteredPosition;
1854 
1855  if (getFilteredOdometryMessage(filteredPosition))
1856  {
1857  worldBaseLinkTransMsg_.header.stamp = filteredPosition.header.stamp + tfTimeOffset_;
1858  worldBaseLinkTransMsg_.header.frame_id = filteredPosition.header.frame_id;
1859  worldBaseLinkTransMsg_.child_frame_id = filteredPosition.child_frame_id;
1860 
1861  worldBaseLinkTransMsg_.transform.translation.x = filteredPosition.pose.pose.position.x;
1862  worldBaseLinkTransMsg_.transform.translation.y = filteredPosition.pose.pose.position.y;
1863  worldBaseLinkTransMsg_.transform.translation.z = filteredPosition.pose.pose.position.z;
1864  worldBaseLinkTransMsg_.transform.rotation = filteredPosition.pose.pose.orientation;
1865 
1866  // the filteredPosition is the message containing the state and covariances: nav_msgs Odometry
1867 
1868  if (!validateFilterOutput(filteredPosition))
1869  {
1870  ROS_ERROR_STREAM("Critical Error, NaNs were detected in the output state of the filter." <<
1871  " This was likely due to poorly coniditioned process, noise, or sensor covariances.");
1872  }
1873 
1874  // If the worldFrameId_ is the odomFrameId_ frame, then we can just send the transform. If the
1875  // worldFrameId_ is the mapFrameId_ frame, we'll have some work to do.
1876  if (publishTransform_)
1877  {
1878  if (filteredPosition.header.frame_id == odomFrameId_)
1879  {
1880  worldTransformBroadcaster.sendTransform(worldBaseLinkTransMsg_);
1881  }
1882  else if (filteredPosition.header.frame_id == mapFrameId_)
1883  {
1884  try
1885  {
1886  tf2::Transform worldBaseLinkTrans;
1887  tf2::fromMsg(worldBaseLinkTransMsg_.transform, worldBaseLinkTrans);
1888 
1890  tfBuffer_,
1892  odomFrameId_,
1893  ros::Time(filter_.getLastMeasurementTime()),
1894  baseLinkOdomTrans,
1895  true))
1896  {
1897  ROS_ERROR_STREAM_DELAYED_THROTTLE(1.0, "Unable to retrieve " << odomFrameId_ << "->" <<
1898  baseLinkFrameId_ << " transform. Skipping iteration...");
1899  continue;
1900  }
1901 
1902  /*
1903  * First, see these two references:
1904  * http://wiki.ros.org/tf/Overview/Using%20Published%20Transforms#lookupTransform
1905  * http://wiki.ros.org/geometry/CoordinateFrameConventions#Transform_Direction
1906  * We have a transform from mapFrameId_->baseLinkFrameId_, but it would actually transform
1907  * a given pose from baseLinkFrameId_->mapFrameId_. We then used lookupTransform, whose
1908  * first two arguments are target frame and source frame, to get a transform from
1909  * baseLinkFrameId_->odomFrameId_. However, this transform would actually transform data
1910  * from odomFrameId_->baseLinkFrameId_. Now imagine that we have a position in the
1911  * mapFrameId_ frame. First, we multiply it by the inverse of the
1912  * mapFrameId_->baseLinkFrameId, which will transform that data from mapFrameId_ to
1913  * baseLinkFrameId_. Now we want to go from baseLinkFrameId_->odomFrameId_, but the
1914  * transform we have takes data from odomFrameId_->baseLinkFrameId_, so we need its
1915  * inverse as well. We have now transformed our data from mapFrameId_ to odomFrameId_.
1916  * However, if we want other users to be able to do the same, we need to broadcast
1917  * the inverse of that entire transform.
1918  */
1919 
1920  mapOdomTrans.mult(worldBaseLinkTrans, baseLinkOdomTrans);
1921 
1922  mapOdomTransMsg.transform = tf2::toMsg(mapOdomTrans);
1923  mapOdomTransMsg.header.stamp = filteredPosition.header.stamp + tfTimeOffset_;
1924  mapOdomTransMsg.header.frame_id = mapFrameId_;
1925  mapOdomTransMsg.child_frame_id = odomFrameId_;
1926 
1927  worldTransformBroadcaster.sendTransform(mapOdomTransMsg);
1928  }
1929  catch(...)
1930  {
1931  ROS_ERROR_STREAM_DELAYED_THROTTLE(5.0, "Could not obtain transform from "
1932  << odomFrameId_ << "->" << baseLinkFrameId_);
1933  }
1934  }
1935  else
1936  {
1937  ROS_ERROR_STREAM("Odometry message frame_id was " << filteredPosition.header.frame_id <<
1938  ", expected " << mapFrameId_ << " or " << odomFrameId_);
1939  }
1940  }
1941 
1942  // Fire off the position and the transform
1943  positionPub.publish(filteredPosition);
1944 
1945  if (printDiagnostics_)
1946  {
1947  freqDiag.tick();
1948  }
1949  }
1950 
1951  // Publish the acceleration if desired and filter is initialized
1952  geometry_msgs::AccelWithCovarianceStamped filteredAcceleration;
1953  if (publishAcceleration_ && getFilteredAccelMessage(filteredAcceleration))
1954  {
1955  accelPub.publish(filteredAcceleration);
1956  }
1957 
1958  /* Diagnostics can behave strangely when playing back from bag
1959  * files and using simulated time, so we have to check for
1960  * time suddenly moving backwards as well as the standard
1961  * timeout criterion before publishing. */
1962  double diagDuration = (curTime - lastDiagTime).toSec();
1963  if (printDiagnostics_ && (diagDuration >= diagnosticUpdater_.getPeriod() || diagDuration < 0.0))
1964  {
1966  lastDiagTime = curTime;
1967  }
1968 
1969  // Clear out expired history data
1970  if (smoothLaggedData_)
1971  {
1972  clearExpiredHistory(filter_.getLastMeasurementTime() - historyLength_);
1973  }
1974 
1975  ros::Duration loop_elapsed_time = ros::Time::now() - loop_end_time;
1976 
1977  if (loop_elapsed_time > loop_cycle_time)
1978  {
1979  ROS_WARN_STREAM_DELAYED_THROTTLE(1.0, "Failed to meet update rate! Took " << std::setprecision(20) <<
1980  loop_elapsed_time.toSec() << " seconds. Try decreasing the rate, limiting sensor output frequency, or "
1981  "limiting the number of sensors.");
1982  }
1983  else
1984  {
1985  ros::Duration sleep_time = loop_cycle_time - loop_elapsed_time;
1986  sleep_time.sleep();
1987  }
1988 
1989  loop_end_time = ros::Time::now();
1990  }
1991  }
1992 
1993  template<typename T>
1994  void RosFilter<T>::setPoseCallback(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg)
1995  {
1996  RF_DEBUG("------ RosFilter::setPoseCallback ------\nPose message:\n" << *msg);
1997 
1998  ROS_INFO_STREAM("Received set_pose request with value\n" << *msg);
1999 
2000  std::string topicName("setPose");
2001 
2002  // Get rid of any initial poses (pretend we've never had a measurement)
2003  initialMeasurements_.clear();
2004  previousMeasurements_.clear();
2006 
2008 
2009  filterStateHistory_.clear();
2010  measurementHistory_.clear();
2011 
2012  // Also set the last set pose time, so we ignore all messages
2013  // that occur before it
2014  lastSetPoseTime_ = msg->header.stamp;
2015 
2016  // Set the state vector to the reported pose
2017  Eigen::VectorXd measurement(STATE_SIZE);
2018  Eigen::MatrixXd measurementCovariance(STATE_SIZE, STATE_SIZE);
2019  std::vector<int> updateVector(STATE_SIZE, true);
2020 
2021  // We only measure pose variables, so initialize the vector to 0
2022  measurement.setZero();
2023 
2024  // Set this to the identity and let the message reset it
2025  measurementCovariance.setIdentity();
2026  measurementCovariance *= 1e-6;
2027 
2028  // Prepare the pose data (really just using this to transform it into the target frame).
2029  // Twist data is going to get zeroed out.
2030  preparePose(msg, topicName, worldFrameId_, false, false, false, updateVector, measurement, measurementCovariance);
2031 
2032  // For the state
2033  filter_.setState(measurement);
2034  filter_.setEstimateErrorCovariance(measurementCovariance);
2035 
2036  filter_.setLastMeasurementTime(ros::Time::now().toSec());
2037 
2038  RF_DEBUG("\n------ /RosFilter::setPoseCallback ------\n");
2039  }
2040 
2041  template<typename T>
2042  bool RosFilter<T>::setPoseSrvCallback(robot_localization::SetPose::Request& request,
2043  robot_localization::SetPose::Response&)
2044  {
2045  geometry_msgs::PoseWithCovarianceStamped::Ptr msg;
2046  msg = boost::make_shared<geometry_msgs::PoseWithCovarianceStamped>(request.pose);
2047  setPoseCallback(msg);
2048 
2049  return true;
2050  }
2051 
2052  template<typename T>
2053  bool RosFilter<T>::enableFilterSrvCallback(std_srvs::Empty::Request&,
2054  std_srvs::Empty::Response&)
2055  {
2056  RF_DEBUG("\n[" << ros::this_node::getName() << ":]" << " ------ /RosFilter::enableFilterSrvCallback ------\n");
2057  if (enabled_)
2058  {
2060  ":] Asking for enabling filter service, but the filter was already enabled! Use param disabled_at_startup.");
2061  }
2062  else
2063  {
2064  ROS_INFO_STREAM("[" << ros::this_node::getName() << ":] Enabling filter...");
2065  enabled_ = true;
2066  }
2067  return true;
2068  }
2069 
2070  template<typename T>
2071  void RosFilter<T>::twistCallback(const geometry_msgs::TwistWithCovarianceStamped::ConstPtr &msg,
2072  const CallbackData &callbackData,
2073  const std::string &targetFrame)
2074  {
2075  const std::string &topicName = callbackData.topicName_;
2076 
2077  // If we've just reset the filter, then we want to ignore any messages
2078  // that arrive with an older timestamp
2079  if (msg->header.stamp <= lastSetPoseTime_)
2080  {
2081  std::stringstream stream;
2082  stream << "The " << topicName << " message has a timestamp equal to or before the last filter reset, " <<
2083  "this message will be ignored. This may indicate an empty or bad timestamp. (message time: " <<
2084  msg->header.stamp.toSec() << ")";
2085  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2086  topicName + "_timestamp",
2087  stream.str(),
2088  false);
2089  return;
2090  }
2091 
2092  RF_DEBUG("------ RosFilter::twistCallback (" << topicName << ") ------\n"
2093  "Twist message:\n" << *msg);
2094 
2095  if (lastMessageTimes_.count(topicName) == 0)
2096  {
2097  lastMessageTimes_.insert(std::pair<std::string, ros::Time>(topicName, msg->header.stamp));
2098  }
2099 
2100  // Make sure this message is newer than the last one
2101  if (msg->header.stamp >= lastMessageTimes_[topicName])
2102  {
2103  RF_DEBUG("Update vector for " << topicName << " is:\n" << callbackData.updateVector_);
2104 
2105  Eigen::VectorXd measurement(STATE_SIZE);
2106  Eigen::MatrixXd measurementCovariance(STATE_SIZE, STATE_SIZE);
2107 
2108  measurement.setZero();
2109  measurementCovariance.setZero();
2110 
2111  // Make sure we're actually updating at least one of these variables
2112  std::vector<int> updateVectorCorrected = callbackData.updateVector_;
2113 
2114  // Prepare the twist data for inclusion in the filter
2115  if (prepareTwist(msg, topicName, targetFrame, updateVectorCorrected, measurement, measurementCovariance))
2116  {
2117  // Store the measurement. Add a "twist" suffix so we know what kind of measurement
2118  // we're dealing with when we debug the core filter logic.
2119  enqueueMeasurement(topicName,
2120  measurement,
2121  measurementCovariance,
2122  updateVectorCorrected,
2123  callbackData.rejectionThreshold_,
2124  msg->header.stamp);
2125 
2126  RF_DEBUG("Enqueued new measurement for " << topicName << "_twist\n");
2127  }
2128  else
2129  {
2130  RF_DEBUG("Did *not* enqueue measurement for " << topicName << "_twist\n");
2131  }
2132 
2133  lastMessageTimes_[topicName] = msg->header.stamp;
2134 
2135  RF_DEBUG("Last message time for " << topicName << " is now " <<
2136  lastMessageTimes_[topicName] << "\n");
2137  }
2138  else if (resetOnTimeJump_ && ros::Time::isSimTime())
2139  {
2140  reset();
2141  }
2142  else
2143  {
2144  std::stringstream stream;
2145  stream << "The " << topicName << " message has a timestamp before that of the previous message received," <<
2146  " this message will be ignored. This may indicate a bad timestamp. (message time: " <<
2147  msg->header.stamp.toSec() << ")";
2148  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2149  topicName + "_timestamp",
2150  stream.str(),
2151  false);
2152 
2153  RF_DEBUG("Message is too old. Last message time for " << topicName << " is " << lastMessageTimes_[topicName] <<
2154  ", current message time is " << msg->header.stamp << ".\n");
2155  }
2156 
2157  RF_DEBUG("\n----- /RosFilter::twistCallback (" << topicName << ") ------\n");
2158  }
2159 
2160  template<typename T>
2161  void RosFilter<T>::addDiagnostic(const int errLevel,
2162  const std::string &topicAndClass,
2163  const std::string &message,
2164  const bool staticDiag)
2165  {
2166  if (staticDiag)
2167  {
2168  staticDiagnostics_[topicAndClass] = message;
2169  staticDiagErrorLevel_ = std::max(staticDiagErrorLevel_, errLevel);
2170  }
2171  else
2172  {
2173  dynamicDiagnostics_[topicAndClass] = message;
2174  dynamicDiagErrorLevel_ = std::max(dynamicDiagErrorLevel_, errLevel);
2175  }
2176  }
2177 
2178  template<typename T>
2180  {
2181  wrapper.clear();
2182  wrapper.clearSummary();
2183 
2184  int maxErrLevel = std::max(staticDiagErrorLevel_, dynamicDiagErrorLevel_);
2185 
2186  // Report the overall status
2187  switch (maxErrLevel)
2188  {
2189  case diagnostic_msgs::DiagnosticStatus::ERROR:
2190  wrapper.summary(maxErrLevel,
2191  "Erroneous data or settings detected for a robot_localization state estimation node.");
2192  break;
2193  case diagnostic_msgs::DiagnosticStatus::WARN:
2194  wrapper.summary(maxErrLevel,
2195  "Potentially erroneous data or settings detected for "
2196  "a robot_localization state estimation node.");
2197  break;
2198  case diagnostic_msgs::DiagnosticStatus::STALE:
2199  wrapper.summary(maxErrLevel,
2200  "The state of the robot_localization state estimation node is stale.");
2201  break;
2202  case diagnostic_msgs::DiagnosticStatus::OK:
2203  wrapper.summary(maxErrLevel,
2204  "The robot_localization state estimation node appears to be functioning properly.");
2205  break;
2206  default:
2207  break;
2208  }
2209 
2210  // Aggregate all the static messages
2211  for (std::map<std::string, std::string>::iterator diagIt = staticDiagnostics_.begin();
2212  diagIt != staticDiagnostics_.end();
2213  ++diagIt)
2214  {
2215  wrapper.add(diagIt->first, diagIt->second);
2216  }
2217 
2218  // Aggregate all the dynamic messages, then clear them
2219  for (std::map<std::string, std::string>::iterator diagIt = dynamicDiagnostics_.begin();
2220  diagIt != dynamicDiagnostics_.end();
2221  ++diagIt)
2222  {
2223  wrapper.add(diagIt->first, diagIt->second);
2224  }
2225  dynamicDiagnostics_.clear();
2226 
2227  // Reset the warning level for the dynamic diagnostic messages
2228  dynamicDiagErrorLevel_ = diagnostic_msgs::DiagnosticStatus::OK;
2229  }
2230 
2231  template<typename T>
2232  void RosFilter<T>::copyCovariance(const double *arr,
2233  Eigen::MatrixXd &covariance,
2234  const std::string &topicName,
2235  const std::vector<int> &updateVector,
2236  const size_t offset,
2237  const size_t dimension)
2238  {
2239  for (size_t i = 0; i < dimension; i++)
2240  {
2241  for (size_t j = 0; j < dimension; j++)
2242  {
2243  covariance(i, j) = arr[dimension * i + j];
2244 
2245  if (printDiagnostics_)
2246  {
2247  std::string iVar = stateVariableNames_[offset + i];
2248 
2249  if (covariance(i, j) > 1e3 && (updateVector[offset + i] || updateVector[offset + j]))
2250  {
2251  std::string jVar = stateVariableNames_[offset + j];
2252 
2253  std::stringstream stream;
2254  stream << "The covariance at position (" << dimension * i + j << "), which corresponds to " <<
2255  (i == j ? iVar + " variance" : iVar + " and " + jVar + " covariance") <<
2256  ", the value is extremely large (" << covariance(i, j) << "), but the update vector for " <<
2257  (i == j ? iVar : iVar + " and/or " + jVar) << " is set to true. This may produce undesirable results.";
2258 
2259  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2260  topicName + "_covariance",
2261  stream.str(),
2262  false);
2263  }
2264  else if (updateVector[i] && i == j && covariance(i, j) == 0)
2265  {
2266  std::stringstream stream;
2267  stream << "The covariance at position (" << dimension * i + j << "), which corresponds to " <<
2268  iVar << " variance, was zero. This will be replaced with a small value to maintain filter "
2269  "stability, but should be corrected at the message origin node.";
2270 
2271  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2272  topicName + "_covariance",
2273  stream.str(),
2274  false);
2275  }
2276  else if (updateVector[i] && i == j && covariance(i, j) < 0)
2277  {
2278  std::stringstream stream;
2279  stream << "The covariance at position (" << dimension * i + j <<
2280  "), which corresponds to " << iVar << " variance, was negative. This will be replaced with a "
2281  "small positive value to maintain filter stability, but should be corrected at the message "
2282  "origin node.";
2283 
2284  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2285  topicName + "_covariance",
2286  stream.str(),
2287  false);
2288  }
2289  }
2290  }
2291  }
2292  }
2293 
2294  template<typename T>
2295  void RosFilter<T>::copyCovariance(const Eigen::MatrixXd &covariance,
2296  double *arr,
2297  const size_t dimension)
2298  {
2299  for (size_t i = 0; i < dimension; i++)
2300  {
2301  for (size_t j = 0; j < dimension; j++)
2302  {
2303  arr[dimension * i + j] = covariance(i, j);
2304  }
2305  }
2306  }
2307 
2308  template<typename T>
2309  std::vector<int> RosFilter<T>::loadUpdateConfig(const std::string &topicName)
2310  {
2311  XmlRpc::XmlRpcValue topicConfig;
2312  std::vector<int> updateVector(STATE_SIZE, 0);
2313  std::string topicConfigName = topicName + "_config";
2314 
2315  try
2316  {
2317  nhLocal_.getParam(topicConfigName, topicConfig);
2318 
2320 
2321  if (topicConfig.size() != STATE_SIZE)
2322  {
2323  ROS_WARN_STREAM("Configuration vector for " << topicConfigName << " should have 15 entries.");
2324  }
2325 
2326  for (int i = 0; i < topicConfig.size(); i++)
2327  {
2328  // The double cast looks strange, but we'll get exceptions if we
2329  // remove the cast to bool. vector<bool> is discouraged, so updateVector
2330  // uses integers.
2331  updateVector[i] = static_cast<int>(static_cast<bool>(topicConfig[i]));
2332  }
2333  }
2334  catch (XmlRpc::XmlRpcException &e)
2335  {
2336  ROS_FATAL_STREAM("Could not read sensor update configuration for topic " << topicName <<
2337  " (type: " << topicConfig.getType() << ", expected: " << XmlRpc::XmlRpcValue::TypeArray
2338  << "). Error was " << e.getMessage() << "\n");
2339  }
2340 
2341  return updateVector;
2342  }
2343 
2344  template<typename T>
2345  bool RosFilter<T>::prepareAcceleration(const sensor_msgs::Imu::ConstPtr &msg,
2346  const std::string &topicName,
2347  const std::string &targetFrame,
2348  std::vector<int> &updateVector,
2349  Eigen::VectorXd &measurement,
2350  Eigen::MatrixXd &measurementCovariance)
2351  {
2352  RF_DEBUG("------ RosFilter::prepareAcceleration (" << topicName << ") ------\n");
2353 
2354  // 1. Get the measurement into a vector
2355  tf2::Vector3 accTmp(msg->linear_acceleration.x,
2356  msg->linear_acceleration.y,
2357  msg->linear_acceleration.z);
2358 
2359  // Set relevant header info
2360  std::string msgFrame = (msg->header.frame_id == "" ? baseLinkFrameId_ : msg->header.frame_id);
2361 
2362  // 2. robot_localization lets users configure which variables from the sensor should be
2363  // fused with the filter. This is specified at the sensor level. However, the data
2364  // may go through transforms before being fused with the state estimate. In that case,
2365  // we need to know which of the transformed variables came from the pre-transformed
2366  // "approved" variables (i.e., the ones that had "true" in their xxx_config parameter).
2367  // To do this, we create a pose from the original upate vector, which contains only
2368  // zeros and ones. This pose goes through the same transforms as the measurement. The
2369  // non-zero values that result will be used to modify the updateVector.
2370  tf2::Matrix3x3 maskAcc(updateVector[StateMemberAx], 0, 0,
2371  0, updateVector[StateMemberAy], 0,
2372  0, 0, updateVector[StateMemberAz]);
2373 
2374  // 3. We'll need to rotate the covariance as well
2375  Eigen::MatrixXd covarianceRotated(ACCELERATION_SIZE, ACCELERATION_SIZE);
2376  covarianceRotated.setZero();
2377 
2378  this->copyCovariance(&(msg->linear_acceleration_covariance[0]),
2379  covarianceRotated,
2380  topicName,
2381  updateVector,
2384 
2385  RF_DEBUG("Original measurement as tf object: " << accTmp <<
2386  "\nOriginal update vector:\n" << updateVector <<
2387  "\nOriginal covariance matrix:\n" << covarianceRotated << "\n");
2388 
2389  // 4. We need to transform this into the target frame (probably base_link)
2390  // It's unlikely that we'll get a velocity measurement in another frame, but
2391  // we have to handle the situation.
2392  tf2::Transform targetFrameTrans;
2394  targetFrame,
2395  msgFrame,
2396  msg->header.stamp,
2397  tfTimeout_,
2398  targetFrameTrans);
2399 
2400  if (canTransform)
2401  {
2402  // We don't know if the user has already handled the removal
2403  // of normal forces, so we use a parameter
2404  if (removeGravitationalAcc_[topicName])
2405  {
2406  tf2::Vector3 normAcc(0, 0, gravitationalAcc_);
2407  tf2::Quaternion curAttitude;
2408  tf2::Transform trans;
2409 
2410  if (::fabs(msg->orientation_covariance[0] + 1) < 1e-9)
2411  {
2412  // Imu message contains no orientation, so we should use orientation
2413  // from filter state to transform and remove acceleration
2414  const Eigen::VectorXd &state = filter_.getState();
2415  tf2::Vector3 stateTmp(state(StateMemberRoll),
2416  state(StateMemberPitch),
2417  state(StateMemberYaw));
2418  // transform state orientation to IMU frame
2419  tf2::Transform imuFrameTrans;
2421  msgFrame,
2422  targetFrame,
2423  msg->header.stamp,
2424  tfTimeout_,
2425  imuFrameTrans);
2426  stateTmp = imuFrameTrans.getBasis() * stateTmp;
2427  curAttitude.setRPY(stateTmp.getX(), stateTmp.getY(), stateTmp.getZ());
2428  }
2429  else
2430  {
2431  tf2::fromMsg(msg->orientation, curAttitude);
2432  if (fabs(curAttitude.length() - 1.0) > 0.01)
2433  {
2434  ROS_WARN_ONCE("An input was not normalized, this should NOT happen, but will normalize.");
2435  curAttitude.normalize();
2436  }
2437  }
2438  trans.setRotation(curAttitude);
2439  tf2::Vector3 rotNorm = trans.getBasis().inverse() * normAcc;
2440  accTmp.setX(accTmp.getX() - rotNorm.getX());
2441  accTmp.setY(accTmp.getY() - rotNorm.getY());
2442  accTmp.setZ(accTmp.getZ() - rotNorm.getZ());
2443 
2444  RF_DEBUG("Orientation is " << curAttitude <<
2445  "Acceleration due to gravity is " << rotNorm <<
2446  "After removing acceleration due to gravity, acceleration is " << accTmp << "\n");
2447  }
2448 
2449  // Transform to correct frame
2450  // @todo: This needs to take into account offsets from the origin. Right now,
2451  // it assumes that if the sensor is placed at some non-zero offset from the
2452  // vehicle's center, that the vehicle turns with constant velocity. This needs
2453  // to be something like
2454  // accTmp = targetFrameTrans.getBasis() * accTmp - targetFrameTrans.getOrigin().cross(rotation_acceleration);
2455  // We can get rotational acceleration by differentiating the rotational velocity
2456  // (if it's available)
2457  accTmp = targetFrameTrans.getBasis() * accTmp;
2458  maskAcc = targetFrameTrans.getBasis() * maskAcc;
2459 
2460  // Now use the mask values to determine which update vector values should be true
2461  updateVector[StateMemberAx] = static_cast<int>(
2462  maskAcc.getRow(StateMemberAx - POSITION_A_OFFSET).length() >= 1e-6);
2463  updateVector[StateMemberAy] = static_cast<int>(
2464  maskAcc.getRow(StateMemberAy - POSITION_A_OFFSET).length() >= 1e-6);
2465  updateVector[StateMemberAz] = static_cast<int>(
2466  maskAcc.getRow(StateMemberAz - POSITION_A_OFFSET).length() >= 1e-6);
2467 
2468  RF_DEBUG(msg->header.frame_id << "->" << targetFrame << " transform:\n" << targetFrameTrans <<
2469  "\nAfter applying transform to " << targetFrame << ", update vector is:\n" << updateVector <<
2470  "\nAfter applying transform to " << targetFrame << ", measurement is:\n" << accTmp << "\n");
2471 
2472  // 5. Now rotate the covariance: create an augmented
2473  // matrix that contains a 3D rotation matrix in the
2474  // upper-left and lower-right quadrants, and zeros
2475  // elsewhere
2476  tf2::Matrix3x3 rot(targetFrameTrans.getRotation());
2477  Eigen::MatrixXd rot3d(ACCELERATION_SIZE, ACCELERATION_SIZE);
2478  rot3d.setIdentity();
2479 
2480  for (size_t rInd = 0; rInd < ACCELERATION_SIZE; ++rInd)
2481  {
2482  rot3d(rInd, 0) = rot.getRow(rInd).getX();
2483  rot3d(rInd, 1) = rot.getRow(rInd).getY();
2484  rot3d(rInd, 2) = rot.getRow(rInd).getZ();
2485  }
2486 
2487  // Carry out the rotation
2488  covarianceRotated = rot3d * covarianceRotated.eval() * rot3d.transpose();
2489 
2490  RF_DEBUG("Transformed covariance is \n" << covarianceRotated << "\n");
2491 
2492  // 6. Store our corrected measurement and covariance
2493  measurement(StateMemberAx) = accTmp.getX();
2494  measurement(StateMemberAy) = accTmp.getY();
2495  measurement(StateMemberAz) = accTmp.getZ();
2496 
2497  // Copy the covariances
2498  measurementCovariance.block(POSITION_A_OFFSET, POSITION_A_OFFSET, ACCELERATION_SIZE, ACCELERATION_SIZE) =
2499  covarianceRotated.block(0, 0, ACCELERATION_SIZE, ACCELERATION_SIZE);
2500 
2501  // 7. Handle 2D mode
2502  if (twoDMode_)
2503  {
2504  forceTwoD(measurement, measurementCovariance, updateVector);
2505  }
2506  }
2507  else
2508  {
2509  RF_DEBUG("Could not transform measurement into " << targetFrame << ". Ignoring...\n");
2510  }
2511 
2512  RF_DEBUG("\n----- /RosFilter::prepareAcceleration(" << topicName << ") ------\n");
2513 
2514  return canTransform;
2515  }
2516 
2517  template<typename T>
2518  bool RosFilter<T>::preparePose(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg,
2519  const std::string &topicName,
2520  const std::string &targetFrame,
2521  const bool differential,
2522  const bool relative,
2523  const bool imuData,
2524  std::vector<int> &updateVector,
2525  Eigen::VectorXd &measurement,
2526  Eigen::MatrixXd &measurementCovariance)
2527  {
2528  bool retVal = false;
2529 
2530  RF_DEBUG("------ RosFilter::preparePose (" << topicName << ") ------\n");
2531 
2532  // 1. Get the measurement into a tf-friendly transform (pose) object
2534 
2535  // We'll need this later for storing this measurement for differential integration
2536  tf2::Transform curMeasurement;
2537 
2538  // Handle issues where frame_id data is not filled out properly
2539  // @todo: verify that this is necessary still. New IMU handling may
2540  // have rendered this obsolete.
2541  std::string finalTargetFrame;
2542  if (targetFrame == "" && msg->header.frame_id == "")
2543  {
2544  // Blank target and message frames mean we can just
2545  // use our world_frame
2546  finalTargetFrame = worldFrameId_;
2547  poseTmp.frame_id_ = finalTargetFrame;
2548  }
2549  else if (targetFrame == "")
2550  {
2551  // A blank target frame means we shouldn't bother
2552  // transforming the data
2553  finalTargetFrame = msg->header.frame_id;
2554  poseTmp.frame_id_ = finalTargetFrame;
2555  }
2556  else
2557  {
2558  // Otherwise, we should use our target frame
2559  finalTargetFrame = targetFrame;
2560  poseTmp.frame_id_ = (differential ? finalTargetFrame : msg->header.frame_id);
2561  }
2562 
2563  RF_DEBUG("Final target frame for " << topicName << " is " << finalTargetFrame << "\n");
2564 
2565  poseTmp.stamp_ = msg->header.stamp;
2566 
2567  // Fill out the position data
2568  poseTmp.setOrigin(tf2::Vector3(msg->pose.pose.position.x,
2569  msg->pose.pose.position.y,
2570  msg->pose.pose.position.z));
2571 
2572  tf2::Quaternion orientation;
2573 
2574  // Handle bad (empty) quaternions
2575  if (msg->pose.pose.orientation.x == 0 && msg->pose.pose.orientation.y == 0 &&
2576  msg->pose.pose.orientation.z == 0 && msg->pose.pose.orientation.w == 0)
2577  {
2578  orientation.setValue(0.0, 0.0, 0.0, 1.0);
2579 
2580  if (updateVector[StateMemberRoll] || updateVector[StateMemberPitch] || updateVector[StateMemberYaw])
2581  {
2582  std::stringstream stream;
2583  stream << "The " << topicName << " message contains an invalid orientation quaternion, " <<
2584  "but its configuration is such that orientation data is being used. Correcting...";
2585 
2586  addDiagnostic(diagnostic_msgs::DiagnosticStatus::WARN,
2587  topicName + "_orientation",
2588  stream.str(),
2589  false);
2590  }
2591  }
2592  else
2593  {
2594  tf2::fromMsg(msg->pose.pose.orientation, orientation);
2595  if (fabs(orientation.length() - 1.0) > 0.01)
2596  {
2597  ROS_WARN_ONCE("An input was not normalized, this should NOT happen, but will normalize.");
2598  orientation.normalize();
2599  }
2600  }
2601 
2602  // Fill out the orientation data
2603  poseTmp.setRotation(orientation);
2604 
2605  // 2. Get the target frame transformation
2606  tf2::Transform targetFrameTrans;
2608  finalTargetFrame,
2609  poseTmp.frame_id_,
2610  poseTmp.stamp_,
2611  tfTimeout_,
2612  targetFrameTrans);
2613 
2614  // 3. Make sure we can work with this data before carrying on
2615  if (canTransform)
2616  {
2617  /* 4. robot_localization lets users configure which variables from the sensor should be
2618  * fused with the filter. This is specified at the sensor level. However, the data
2619  * may go through transforms before being fused with the state estimate. In that case,
2620  * we need to know which of the transformed variables came from the pre-transformed
2621  * "approved" variables (i.e., the ones that had "true" in their xxx_config parameter).
2622  * To do this, we construct matrices using the update vector values on the diagonals,
2623  * pass this matrix through the rotation, and use the length of each row to determine
2624  * the transformed update vector. The process is slightly different for IMUs, as the
2625  * coordinate frame transform is really the base_link->imu_frame transform, and not
2626  * a transform from some other world-fixed frame (even though the IMU data itself *is*
2627  * reported in a world fixed frame). */
2628  tf2::Matrix3x3 maskPosition(updateVector[StateMemberX], 0, 0,
2629  0, updateVector[StateMemberY], 0,
2630  0, 0, updateVector[StateMemberZ]);
2631 
2632  tf2::Matrix3x3 maskOrientation(updateVector[StateMemberRoll], 0, 0,
2633  0, updateVector[StateMemberPitch], 0,
2634  0, 0, updateVector[StateMemberYaw]);
2635 
2636  if (imuData)
2637  {
2638  /* We have to treat IMU orientation data differently. Even though we are dealing with pose
2639  * data when we work with orientations, for IMUs, the frame_id is the frame in which the
2640  * sensor is mounted, and not the coordinate frame of the IMU. Imagine an IMU that is mounted
2641  * facing sideways. The pitch in the IMU frame becomes roll for the vehicle. This means that
2642  * we need to rotate roll and pitch angles by the IMU's mounting yaw offset, and we must apply
2643  * similar treatment to its update mask and covariance. */
2644 
2645  double dummy, yaw;
2646  targetFrameTrans.getBasis().getRPY(dummy, dummy, yaw);
2647  tf2::Matrix3x3 transTmp;
2648  transTmp.setRPY(0.0, 0.0, yaw);
2649 
2650  maskPosition = transTmp * maskPosition;
2651  maskOrientation = transTmp * maskOrientation;
2652  }
2653  else
2654  {
2655  maskPosition = targetFrameTrans.getBasis() * maskPosition;
2656  maskOrientation = targetFrameTrans.getBasis() * maskOrientation;
2657  }
2658 
2659  // Now copy the mask values back into the update vector: any row with a significant vector length
2660  // indicates that we want to set that variable to true in the update vector.
2661  updateVector[StateMemberX] = static_cast<int>(
2662  maskPosition.getRow(StateMemberX - POSITION_OFFSET).length() >= 1e-6);
2663  updateVector[StateMemberY] = static_cast<int>(
2664  maskPosition.getRow(StateMemberY - POSITION_OFFSET).length() >= 1e-6);
2665  updateVector[StateMemberZ] = static_cast<int>(
2666  maskPosition.getRow(StateMemberZ - POSITION_OFFSET).length() >= 1e-6);
2667  updateVector[StateMemberRoll] = static_cast<int>(
2668  maskOrientation.getRow(StateMemberRoll - ORIENTATION_OFFSET).length() >= 1e-6);
2669  updateVector[StateMemberPitch] = static_cast<int>(
2670  maskOrientation.getRow(StateMemberPitch - ORIENTATION_OFFSET).length() >= 1e-6);
2671  updateVector[StateMemberYaw] = static_cast<int>(
2672  maskOrientation.getRow(StateMemberYaw - ORIENTATION_OFFSET).length() >= 1e-6);
2673 
2674  // 5a. We'll need to rotate the covariance as well. Create a container and copy over the
2675  // covariance data
2676  Eigen::MatrixXd covariance(POSE_SIZE, POSE_SIZE);
2677  covariance.setZero();
2678  copyCovariance(&(msg->pose.covariance[0]), covariance, topicName, updateVector, POSITION_OFFSET, POSE_SIZE);
2679 
2680  // 5b. Now rotate the covariance: create an augmented matrix that
2681  // contains a 3D rotation matrix in the upper-left and lower-right
2682  // quadrants, with zeros elsewhere.
2683  tf2::Matrix3x3 rot;
2684  Eigen::MatrixXd rot6d(POSE_SIZE, POSE_SIZE);
2685  rot6d.setIdentity();
2686  Eigen::MatrixXd covarianceRotated;
2687 
2688  if (imuData)
2689  {
2690  // Apply the same special logic to the IMU covariance rotation
2691  double dummy, yaw;
2692  targetFrameTrans.getBasis().getRPY(dummy, dummy, yaw);
2693  rot.setRPY(0.0, 0.0, yaw);
2694  }
2695  else
2696  {
2697  rot.setRotation(targetFrameTrans.getRotation());
2698  }
2699 
2700  for (size_t rInd = 0; rInd < POSITION_SIZE; ++rInd)
2701  {
2702  rot6d(rInd, 0) = rot.getRow(rInd).getX();
2703  rot6d(rInd, 1) = rot.getRow(rInd).getY();
2704  rot6d(rInd, 2) = rot.getRow(rInd).getZ();
2705  rot6d(rInd+POSITION_SIZE, 3) = rot.getRow(rInd).getX();
2706  rot6d(rInd+POSITION_SIZE, 4) = rot.getRow(rInd).getY();
2707  rot6d(rInd+POSITION_SIZE, 5) = rot.getRow(rInd).getZ();
2708  }
2709 
2710  // Now carry out the rotation
2711  covarianceRotated = rot6d * covariance * rot6d.transpose();
2712 
2713  RF_DEBUG("After rotating into the " << finalTargetFrame <<
2714  " frame, covariance is \n" << covarianceRotated << "\n");
2715 
2716  /* 6a. For IMU data, the transform that we get is the transform from the body
2717  * frame of the robot (e.g., base_link) to the mounting frame of the robot. It
2718  * is *not* the coordinate frame in which the IMU orientation data is reported.
2719  * If the IMU is mounted in a non-neutral orientation, we need to remove those
2720  * offsets, and then we need to potentially "swap" roll and pitch.
2721  * Note that this transform does NOT handle NED->ENU conversions. Data is assumed
2722  * to be in the ENU frame when it is received.
2723  * */
2724  if (imuData)
2725  {
2726  // First, convert the transform and measurement rotation to RPY
2727  // @todo: There must be a way to handle this with quaternions. Need to look into it.
2728  double rollOffset = 0;
2729  double pitchOffset = 0;
2730  double yawOffset = 0;
2731  double roll = 0;
2732  double pitch = 0;
2733  double yaw = 0;
2734  RosFilterUtilities::quatToRPY(targetFrameTrans.getRotation(), rollOffset, pitchOffset, yawOffset);
2735  RosFilterUtilities::quatToRPY(poseTmp.getRotation(), roll, pitch, yaw);
2736 
2737  // 6b. Apply the offset (making sure to bound them), and throw them in a vector
2738  tf2::Vector3 rpyAngles(FilterUtilities::clampRotation(roll - rollOffset),
2739  FilterUtilities::clampRotation(pitch - pitchOffset),
2740  FilterUtilities::clampRotation(yaw - yawOffset));
2741 
2742  // 6c. Now we need to rotate the roll and pitch by the yaw offset value.
2743  // Imagine a case where an IMU is mounted facing sideways. In that case
2744  // pitch for the IMU's world frame is roll for the robot.
2745  tf2::Matrix3x3 mat;
2746  mat.setRPY(0.0, 0.0, yawOffset);
2747  rpyAngles = mat * rpyAngles;
2748  poseTmp.getBasis().setRPY(rpyAngles.getX(), rpyAngles.getY(), rpyAngles.getZ());
2749 
2750  // We will use this target transformation later on, but
2751  // we've already transformed this data as if the IMU
2752  // were mounted neutrall on the robot, so we can just
2753  // make the transform the identity.
2754  targetFrameTrans.setIdentity();
2755  }
2756 
2757  // 7. Two cases: if we're in differential mode, we need to generate a twist
2758  // message. Otherwise, we just transform it to the target frame.
2759  if (differential)
2760  {
2761  bool success = false;
2762 
2763  // We're going to be playing with poseTmp, so store it,
2764  // as we'll need to save its current value for the next
2765  // measurement.
2766  curMeasurement = poseTmp;
2767 
2768  // Make sure we have previous measurements to work with
2769  if (previousMeasurements_.count(topicName) > 0 && previousMeasurementCovariances_.count(topicName) > 0)
2770  {
2771  // 7a. If we are carrying out differential integration and
2772  // we have a previous measurement for this sensor,then we
2773  // need to apply the inverse of that measurement to this new
2774  // measurement to produce a "delta" measurement between the two.
2775  // Even if we're not using all of the variables from this sensor,
2776  // we need to use the whole measurement to determine the delta
2777  // to the new measurement
2778  tf2::Transform prevMeasurement = previousMeasurements_[topicName];
2779  poseTmp.setData(prevMeasurement.inverseTimes(poseTmp));
2780 
2781  RF_DEBUG("Previous measurement:\n" << previousMeasurements_[topicName] <<
2782  "\nAfter removing previous measurement, measurement delta is:\n" << poseTmp << "\n");
2783 
2784  // 7b. Now we we have a measurement delta in the frame_id of the
2785  // message, but we want that delta to be in the target frame, so
2786  // we need to apply the rotation of the target frame transform.
2787  targetFrameTrans.setOrigin(tf2::Vector3(0.0, 0.0, 0.0));
2788  poseTmp.mult(targetFrameTrans, poseTmp);
2789 
2790  RF_DEBUG("After rotating to the target frame, measurement delta is:\n" << poseTmp << "\n");
2791 
2792  // 7c. Now use the time difference from the last message to compute
2793  // translational and rotational velocities
2794  double dt = msg->header.stamp.toSec() - lastMessageTimes_[topicName].toSec();
2795  double xVel = poseTmp.getOrigin().getX() / dt;
2796  double yVel = poseTmp.getOrigin().getY() / dt;
2797  double zVel = poseTmp.getOrigin().getZ() / dt;
2798 
2799  double rollVel = 0;
2800  double pitchVel = 0;
2801  double yawVel = 0;
2802 
2803  RosFilterUtilities::quatToRPY(poseTmp.getRotation(), rollVel, pitchVel, yawVel);
2804  rollVel /= dt;
2805  pitchVel /= dt;
2806  yawVel /= dt;
2807 
2808  RF_DEBUG("Previous message time was " << lastMessageTimes_[topicName].toSec() <<
2809  ", current message time is " << msg->header.stamp.toSec() << ", delta is " <<
2810  dt << ", velocity is (vX, vY, vZ): (" << xVel << ", " << yVel << ", " << zVel <<
2811  ")\n" << "(vRoll, vPitch, vYaw): (" << rollVel << ", " << pitchVel << ", " <<
2812  yawVel << ")\n");
2813 
2814  // 7d. Fill out the velocity data in the message
2815  geometry_msgs::TwistWithCovarianceStamped *twistPtr = new geometry_msgs::TwistWithCovarianceStamped();
2816  twistPtr->header = msg->header;
2817  twistPtr->header.frame_id = baseLinkFrameId_;
2818  twistPtr->twist.twist.linear.x = xVel;
2819  twistPtr->twist.twist.linear.y = yVel;
2820  twistPtr->twist.twist.linear.z = zVel;
2821  twistPtr->twist.twist.angular.x = rollVel;
2822  twistPtr->twist.twist.angular.y = pitchVel;
2823  twistPtr->twist.twist.angular.z = yawVel;
2824  std::vector<int> twistUpdateVec(STATE_SIZE, false);
2825  std::copy(updateVector.begin() + POSITION_OFFSET,
2826  updateVector.begin() + POSE_SIZE,
2827  twistUpdateVec.begin() + POSITION_V_OFFSET);
2828  std::copy(twistUpdateVec.begin(), twistUpdateVec.end(), updateVector.begin());
2829  geometry_msgs::TwistWithCovarianceStampedConstPtr ptr(twistPtr);
2830 
2831  // 7e. Now rotate the previous covariance for this measurement to get it
2832  // into the target frame, and add the current measurement's rotated covariance
2833  // to the previous measurement's rotated covariance, and multiply by the time delta.
2834  Eigen::MatrixXd prevCovarRotated = rot6d * previousMeasurementCovariances_[topicName] * rot6d.transpose();
2835  covarianceRotated = (covarianceRotated.eval() + prevCovarRotated) * dt;
2836  copyCovariance(covarianceRotated, &(twistPtr->twist.covariance[0]), POSE_SIZE);
2837 
2838  RF_DEBUG("Previous measurement covariance:\n" << previousMeasurementCovariances_[topicName] <<
2839  "\nPrevious measurement covariance rotated:\n" << prevCovarRotated <<
2840  "\nFinal twist covariance:\n" << covarianceRotated << "\n");
2841 
2842  // Now pass this on to prepareTwist, which will convert it to the required frame
2843  success = prepareTwist(ptr,
2844  topicName + "_twist",
2845  twistPtr->header.frame_id,
2846  updateVector,
2847  measurement,
2848  measurementCovariance);
2849  }
2850 
2851  // 7f. Update the previous measurement and measurement covariance
2852  previousMeasurements_[topicName] = curMeasurement;
2853  previousMeasurementCovariances_[topicName] = covariance;
2854 
2855  retVal = success;
2856  }
2857  else
2858  {
2859  // 7g. If we're in relative mode, remove the initial measurement
2860  if (relative)
2861  {
2862  if (initialMeasurements_.count(topicName) == 0)
2863  {
2864  initialMeasurements_.insert(std::pair<std::string, tf2::Transform>(topicName, poseTmp));
2865  }
2866 
2867  tf2::Transform initialMeasurement = initialMeasurements_[topicName];
2868  poseTmp.setData(initialMeasurement.inverseTimes(poseTmp));
2869  }
2870 
2871  // 7h. Apply the target frame transformation to the pose object.
2872  poseTmp.mult(targetFrameTrans, poseTmp);
2873  poseTmp.frame_id_ = finalTargetFrame;
2874 
2875  // 7i. Finally, copy everything into our measurement and covariance objects
2876  measurement(StateMemberX) = poseTmp.getOrigin().x();
2877  measurement(StateMemberY) = poseTmp.getOrigin().y();
2878  measurement(StateMemberZ) = poseTmp.getOrigin().z();
2879 
2880  // The filter needs roll, pitch, and yaw values instead of quaternions
2881  double roll, pitch, yaw;
2882  RosFilterUtilities::quatToRPY(poseTmp.getRotation(), roll, pitch, yaw);
2883  measurement(StateMemberRoll) = roll;
2884  measurement(StateMemberPitch) = pitch;
2885  measurement(StateMemberYaw) = yaw;
2886 
2887  measurementCovariance.block(0, 0, POSE_SIZE, POSE_SIZE) = covarianceRotated.block(0, 0, POSE_SIZE, POSE_SIZE);
2888 
2889  // 8. Handle 2D mode
2890  if (twoDMode_)
2891  {
2892  forceTwoD(measurement, measurementCovariance, updateVector);
2893  }
2894 
2895  retVal = true;
2896  }
2897  }
2898  else
2899  {
2900  retVal = false;
2901 
2902  RF_DEBUG("Could not transform measurement into " << finalTargetFrame << ". Ignoring...");
2903  }
2904 
2905  RF_DEBUG("\n----- /RosFilter::preparePose (" << topicName << ") ------\n");
2906 
2907  return retVal;
2908  }
2909 
2910  template<typename T>
2911  bool RosFilter<T>::prepareTwist(const geometry_msgs::TwistWithCovarianceStamped::ConstPtr &msg,
2912  const std::string &topicName,
2913  const std::string &targetFrame,
2914  std::vector<int> &updateVector,
2915  Eigen::VectorXd &measurement,
2916  Eigen::MatrixXd &measurementCovariance)
2917  {
2918  RF_DEBUG("------ RosFilter::prepareTwist (" << topicName << ") ------\n");
2919 
2920  // 1. Get the measurement into two separate vector objects.
2921  tf2::Vector3 twistLin(msg->twist.twist.linear.x,
2922  msg->twist.twist.linear.y,
2923  msg->twist.twist.linear.z);
2924  tf2::Vector3 measTwistRot(msg->twist.twist.angular.x,
2925  msg->twist.twist.angular.y,
2926  msg->twist.twist.angular.z);
2927 
2928  // 1a. This sensor may or may not measure rotational velocity. Regardless,
2929  // if it measures linear velocity, then later on, we'll need to remove "false"
2930  // linear velocity resulting from angular velocity and the translational offset
2931  // of the sensor from the vehicle origin.
2932  const Eigen::VectorXd &state = filter_.getState();
2933  tf2::Vector3 stateTwistRot(state(StateMemberVroll),
2934  state(StateMemberVpitch),
2935  state(StateMemberVyaw));
2936 
2937  // Determine the frame_id of the data
2938  std::string msgFrame = (msg->header.frame_id == "" ? targetFrame : msg->header.frame_id);
2939 
2940  // 2. robot_localization lets users configure which variables from the sensor should be
2941  // fused with the filter. This is specified at the sensor level. However, the data
2942  // may go through transforms before being fused with the state estimate. In that case,
2943  // we need to know which of the transformed variables came from the pre-transformed
2944  // "approved" variables (i.e., the ones that had "true" in their xxx_config parameter).
2945  // To do this, we construct matrices using the update vector values on the diagonals,
2946  // pass this matrix through the rotation, and use the length of each row to determine
2947  // the transformed update vector.
2948  tf2::Matrix3x3 maskLin(updateVector[StateMemberVx], 0, 0,
2949  0, updateVector[StateMemberVy], 0,
2950  0, 0, updateVector[StateMemberVz]);
2951 
2952  tf2::Matrix3x3 maskRot(updateVector[StateMemberVroll], 0, 0,
2953  0, updateVector[StateMemberVpitch], 0,
2954  0, 0, updateVector[StateMemberVyaw]);
2955 
2956  // 3. We'll need to rotate the covariance as well
2957  Eigen::MatrixXd covarianceRotated(TWIST_SIZE, TWIST_SIZE);
2958  covarianceRotated.setZero();
2959 
2960  copyCovariance(&(msg->twist.covariance[0]),
2961  covarianceRotated,
2962  topicName,
2963  updateVector,
2965  TWIST_SIZE);
2966 
2967  RF_DEBUG("Original measurement as tf object:\nLinear: " << twistLin <<
2968  "Rotational: " << measTwistRot <<
2969  "\nOriginal update vector:\n" << updateVector <<
2970  "\nOriginal covariance matrix:\n" << covarianceRotated << "\n");
2971 
2972  // 4. We need to transform this into the target frame (probably base_link)
2973  tf2::Transform targetFrameTrans;
2975  targetFrame,
2976  msgFrame,
2977  msg->header.stamp,
2978  tfTimeout_,
2979  targetFrameTrans);
2980 
2981  if (canTransform)
2982  {
2983  // Transform to correct frame. Note that we can get linear velocity
2984  // as a result of the sensor offset and rotational velocity
2985  measTwistRot = targetFrameTrans.getBasis() * measTwistRot;
2986  twistLin = targetFrameTrans.getBasis() * twistLin + targetFrameTrans.getOrigin().cross(stateTwistRot);
2987  maskLin = targetFrameTrans.getBasis() * maskLin;
2988  maskRot = targetFrameTrans.getBasis() * maskRot;
2989 
2990  // Now copy the mask values back into the update vector
2991  updateVector[StateMemberVx] = static_cast<int>(
2992  maskLin.getRow(StateMemberVx - POSITION_V_OFFSET).length() >= 1e-6);
2993  updateVector[StateMemberVy] = static_cast<int>(
2994  maskLin.getRow(StateMemberVy - POSITION_V_OFFSET).length() >= 1e-6);
2995  updateVector[StateMemberVz] = static_cast<int>(
2996  maskLin.getRow(StateMemberVz - POSITION_V_OFFSET).length() >= 1e-6);
2997  updateVector[StateMemberVroll] = static_cast<int>(
2998  maskRot.getRow(StateMemberVroll - ORIENTATION_V_OFFSET).length() >= 1e-6);
2999  updateVector[StateMemberVpitch] = static_cast<int>(
3000  maskRot.getRow(StateMemberVpitch - ORIENTATION_V_OFFSET).length() >= 1e-6);
3001  updateVector[StateMemberVyaw] = static_cast<int>(
3002  maskRot.getRow(StateMemberVyaw - ORIENTATION_V_OFFSET).length() >= 1e-6);
3003 
3004  RF_DEBUG(msg->header.frame_id << "->" << targetFrame << " transform:\n" << targetFrameTrans <<
3005  "\nAfter applying transform to " << targetFrame << ", update vector is:\n" << updateVector <<
3006  "\nAfter applying transform to " << targetFrame << ", measurement is:\n" <<
3007  "Linear: " << twistLin << "Rotational: " << measTwistRot << "\n");
3008 
3009  // 5. Now rotate the covariance: create an augmented
3010  // matrix that contains a 3D rotation matrix in the
3011  // upper-left and lower-right quadrants, and zeros
3012  // elsewhere
3013  tf2::Matrix3x3 rot(targetFrameTrans.getRotation());
3014  Eigen::MatrixXd rot6d(TWIST_SIZE, TWIST_SIZE);
3015  rot6d.setIdentity();
3016 
3017  for (size_t rInd = 0; rInd < POSITION_SIZE; ++rInd)
3018  {
3019  rot6d(rInd, 0) = rot.getRow(rInd).getX();
3020  rot6d(rInd, 1) = rot.getRow(rInd).getY();
3021  rot6d(rInd, 2) = rot.getRow(rInd).getZ();
3022  rot6d(rInd+POSITION_SIZE, 3) = rot.getRow(rInd).getX();
3023  rot6d(rInd+POSITION_SIZE, 4) = rot.getRow(rInd).getY();
3024  rot6d(rInd+POSITION_SIZE, 5) = rot.getRow(rInd).getZ();
3025  }
3026 
3027  // Carry out the rotation
3028  covarianceRotated = rot6d * covarianceRotated.eval() * rot6d.transpose();
3029 
3030  RF_DEBUG("Transformed covariance is \n" << covarianceRotated << "\n");
3031 
3032  // 6. Store our corrected measurement and covariance
3033  measurement(StateMemberVx) = twistLin.getX();
3034  measurement(StateMemberVy) = twistLin.getY();
3035  measurement(StateMemberVz) = twistLin.getZ();
3036  measurement(StateMemberVroll) = measTwistRot.getX();
3037  measurement(StateMemberVpitch) = measTwistRot.getY();
3038  measurement(StateMemberVyaw) = measTwistRot.getZ();
3039 
3040  // Copy the covariances
3041  measurementCovariance.block(POSITION_V_OFFSET, POSITION_V_OFFSET, TWIST_SIZE, TWIST_SIZE) =
3042  covarianceRotated.block(0, 0, TWIST_SIZE, TWIST_SIZE);
3043 
3044  // 7. Handle 2D mode
3045  if (twoDMode_)
3046  {
3047  forceTwoD(measurement, measurementCovariance, updateVector);
3048  }
3049  }
3050  else
3051  {
3052  RF_DEBUG("Could not transform measurement into " << targetFrame << ". Ignoring...");
3053  }
3054 
3055  RF_DEBUG("\n----- /RosFilter::prepareTwist (" << topicName << ") ------\n");
3056 
3057  return canTransform;
3058  }
3059 
3060  template<typename T>
3062  {
3063  FilterStatePtr state = FilterStatePtr(new FilterState());
3064  state->state_ = Eigen::VectorXd(filter.getState());
3065  state->estimateErrorCovariance_ = Eigen::MatrixXd(filter.getEstimateErrorCovariance());
3066  state->lastMeasurementTime_ = filter.getLastMeasurementTime();
3067  state->latestControl_ = Eigen::VectorXd(filter.getControl());
3068  state->latestControlTime_ = filter.getControlTime();
3069  filterStateHistory_.push_back(state);
3070  RF_DEBUG("Saved state with timestamp " << std::setprecision(20) << state->lastMeasurementTime_ <<
3071  " to history. " << filterStateHistory_.size() << " measurements are in the queue.\n");
3072  }
3073 
3074  template<typename T>
3075  bool RosFilter<T>::revertTo(const double time)
3076  {
3077  RF_DEBUG("\n----- RosFilter::revertTo -----\n");
3078  RF_DEBUG("\nRequested time was " << std::setprecision(20) << time << "\n")
3079 
3080  size_t history_size = filterStateHistory_.size();
3081 
3082  // Walk back through the queue until we reach a filter state whose time stamp is less than or equal to the
3083  // requested time. Since every saved state after that time will be overwritten/corrected, we can pop from
3084  // the queue. If the history is insufficiently short, we just take the oldest state we have.
3085  FilterStatePtr lastHistoryState;
3086  while (!filterStateHistory_.empty() && filterStateHistory_.back()->lastMeasurementTime_ > time)
3087  {
3088  lastHistoryState = filterStateHistory_.back();
3089  filterStateHistory_.pop_back();
3090  }
3091 
3092  // If the state history is not empty at this point, it means that our history was large enough, and we
3093  // should revert to the state at the back of the history deque.
3094  bool retVal = false;
3095  if (!filterStateHistory_.empty())
3096  {
3097  retVal = true;
3098  lastHistoryState = filterStateHistory_.back();
3099  }
3100  else
3101  {
3102  RF_DEBUG("Insufficient history to revert to time " << time << "\n");
3103 
3104  if (lastHistoryState)
3105  {
3106  RF_DEBUG("Will revert to oldest state at " << lastHistoryState->latestControlTime_ << ".\n");
3107  ROS_WARN_STREAM_DELAYED_THROTTLE(historyLength_, "Could not revert to state with time " <<
3108  std::setprecision(20) << time << ". Instead reverted to state with time " <<
3109  lastHistoryState->lastMeasurementTime_ << ". History size was " << history_size);
3110  }
3111  }
3112 
3113  // If we have a valid reversion state, revert
3114  if (lastHistoryState)
3115  {
3116  // Reset filter to the latest state from the queue.
3117  const FilterStatePtr &state = lastHistoryState;
3118  filter_.setState(state->state_);
3119  filter_.setEstimateErrorCovariance(state->estimateErrorCovariance_);
3120  filter_.setLastMeasurementTime(state->lastMeasurementTime_);
3121 
3122  RF_DEBUG("Reverted to state with time " << std::setprecision(20) << state->lastMeasurementTime_ << "\n");
3123 
3124  // Repeat for measurements, but push every measurement onto the measurement queue as we go
3125  int restored_measurements = 0;
3126  while (!measurementHistory_.empty() && measurementHistory_.back()->time_ > time)
3127  {
3128  // Don't need to restore measurements that predate our earliest state time
3129  if (state->lastMeasurementTime_ <= measurementHistory_.back()->time_)
3130  {
3132  restored_measurements++;
3133  }
3134 
3135  measurementHistory_.pop_back();
3136  }
3137 
3138  RF_DEBUG("Restored " << restored_measurements << " to measurement queue.\n");
3139  }
3140 
3141  RF_DEBUG("\n----- /RosFilter::revertTo\n");
3142 
3143  return retVal;
3144  }
3145 
3146  template<typename T>
3147  bool RosFilter<T>::validateFilterOutput(const nav_msgs::Odometry &message)
3148  {
3149  return !std::isnan(message.pose.pose.position.x) && !std::isinf(message.pose.pose.position.x) &&
3150  !std::isnan(message.pose.pose.position.y) && !std::isinf(message.pose.pose.position.y) &&
3151  !std::isnan(message.pose.pose.position.z) && !std::isinf(message.pose.pose.position.z) &&
3152  !std::isnan(message.pose.pose.orientation.x) && !std::isinf(message.pose.pose.orientation.x) &&
3153  !std::isnan(message.pose.pose.orientation.y) && !std::isinf(message.pose.pose.orientation.y) &&
3154  !std::isnan(message.pose.pose.orientation.z) && !std::isinf(message.pose.pose.orientation.z) &&
3155  !std::isnan(message.pose.pose.orientation.w) && !std::isinf(message.pose.pose.orientation.w) &&
3156  !std::isnan(message.twist.twist.linear.x) && !std::isinf(message.twist.twist.linear.x) &&
3157  !std::isnan(message.twist.twist.linear.y) && !std::isinf(message.twist.twist.linear.y) &&
3158  !std::isnan(message.twist.twist.linear.z) && !std::isinf(message.twist.twist.linear.z) &&
3159  !std::isnan(message.twist.twist.angular.x) && !std::isinf(message.twist.twist.angular.x) &&
3160  !std::isnan(message.twist.twist.angular.y) && !std::isinf(message.twist.twist.angular.y) &&
3161  !std::isnan(message.twist.twist.angular.z) && !std::isinf(message.twist.twist.angular.z);
3162  }
3163 
3164  template<typename T>
3165  void RosFilter<T>::clearExpiredHistory(const double cutOffTime)
3166  {
3167  RF_DEBUG("\n----- RosFilter::clearExpiredHistory -----" <<
3168  "\nCutoff time is " << cutOffTime << "\n");
3169 
3170  int poppedMeasurements = 0;
3171  int poppedStates = 0;
3172 
3173  while (!measurementHistory_.empty() && measurementHistory_.front()->time_ < cutOffTime)
3174  {
3175  measurementHistory_.pop_front();
3176  poppedMeasurements++;
3177  }
3178 
3179  while (!filterStateHistory_.empty() && filterStateHistory_.front()->lastMeasurementTime_ < cutOffTime)
3180  {
3181  filterStateHistory_.pop_front();
3182  poppedStates++;
3183  }
3184 
3185  RF_DEBUG("\nPopped " << poppedMeasurements << " measurements and " <<
3186  poppedStates << " states from their respective queues." <<
3187  "\n---- /RosFilter::clearExpiredHistory ----\n");
3188  }
3189 
3190  template<typename T>
3192  {
3193  while (!measurementQueue_.empty() && ros::ok())
3194  {
3195  measurementQueue_.pop();
3196  }
3197  return;
3198  }
3199 } // namespace RobotLocalization
3200 
3201 // Instantiations of classes is required when template class code
3202 // is placed in a .cpp file.
msg
const std::string & getMessage() const
bool useControl_
Whether or not we use a control term.
Definition: ros_filter.h:627
bool publishAcceleration_
Whether we publish the acceleration.
Definition: ros_filter.h:660
const int STATE_SIZE
Global constants that define our state vector size and offsets to groups of values within that state...
Definition: filter_common.h:75
void setRPY(const tf2Scalar &roll, const tf2Scalar &pitch, const tf2Scalar &yaw)
void getRPY(tf2Scalar &roll, tf2Scalar &pitch, tf2Scalar &yaw, unsigned int solution_number=1) const
const int TWIST_SIZE
Definition: filter_common.h:85
void integrateMeasurements(const ros::Time &currentTime)
Processes all measurements in the measurement queue, in temporal order.
Definition: ros_filter.cpp:524
const int POSITION_A_OFFSET
Definition: filter_common.h:80
bool getFilteredAccelMessage(geometry_msgs::AccelWithCovarianceStamped &message)
Retrieves the EKF&#39;s acceleration output for broadcasting.
Definition: ros_filter.cpp:375
void appendPrefix(std::string tfPrefix, std::string &frameId)
Utility method for appending tf2 prefixes cleanly.
void loadParams()
Loads all parameters from file.
Definition: ros_filter.cpp:644
void odometryCallback(const nav_msgs::Odometry::ConstPtr &msg, const std::string &topicName, const CallbackData &poseCallbackData, const CallbackData &twistCallbackData)
Callback method for receiving all odometry messages.
bool validateFilterOutput(const nav_msgs::Odometry &message)
Validates filter outputs for NaNs and Infinite values.
TF2SIMD_FORCE_INLINE Matrix3x3 & getBasis()
std::vector< int > updateVector_
Definition: ros_filter.h:95
ros::Subscriber controlSub_
Subscribes to the control input topic.
Definition: ros_filter.h:425
void publish(const boost::shared_ptr< M > &message) const
void twistCallback(const geometry_msgs::TwistWithCovarianceStamped::ConstPtr &msg, const CallbackData &callbackData, const std::string &targetFrame)
Callback method for receiving all twist messages.
boost::shared_ptr< FilterState > FilterStatePtr
Definition: filter_base.h:149
ros::NodeHandle nhLocal_
Local node handle (for params)
Definition: ros_filter.h:537
std::map< std::string, Eigen::MatrixXd > previousMeasurementCovariances_
We also need the previous covariance matrix for differential data.
Definition: ros_filter.h:555
const Eigen::VectorXd & getState()
Gets the filter state.
bool resetOnTimeJump_
Whether to reset the filters when backwards jump in time is detected.
Definition: ros_filter.h:472
MeasurementHistoryDeque measurementHistory_
A deque of previous measurements which is implicitly ordered from earliest to latest measurement...
Definition: ros_filter.h:672
void setPoseCallback(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg)
Callback method for manually setting/resetting the internal pose estimate.
void aggregateDiagnostics(diagnostic_updater::DiagnosticStatusWrapper &wrapper)
Aggregates all diagnostics so they can be published.
std::string baseLinkFrameId_
tf frame name for the robot&#39;s body frame
Definition: ros_filter.h:414
std::map< std::string, ros::Time > lastMessageTimes_
Store the last time a message from each topic was received.
Definition: ros_filter.h:510
ros::Subscriber setPoseSub_
Subscribes to the set_pose topic (usually published from rviz). Message type is geometry_msgs/PoseWit...
Definition: ros_filter.h:577
void summary(unsigned char lvl, const std::string s)
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
Quaternion getRotation() const
void accelerationCallback(const sensor_msgs::Imu::ConstPtr &msg, const CallbackData &callbackData, const std::string &targetFrame)
Callback method for receiving all acceleration (IMU) messages.
Definition: ros_filter.cpp:149
TF2SIMD_FORCE_INLINE void setRotation(const Quaternion &q)
bool enabled_
Whether the filter is enabled or not. See disabledAtStartup_.
Definition: ros_filter.h:637
ros::ServiceServer toggleFilterProcessingSrv_
Service that allows another node to toggle on/off filter processing while still publishing. Uses a robot_localization ToggleFilterProcessing service.
Definition: ros_filter.h:595
int size() const
void setHardwareID(const std::string &hwid)
bool sleep() const
Eigen::VectorXd latestControl_
The most recent control input.
Definition: ros_filter.h:476
ros::Duration tfTimeout_
Parameter that specifies the how long we wait for a transform to become available.
Definition: ros_filter.h:484
Structure used for storing and comparing measurements (for priority queues)
Definition: filter_base.h:61
void copyCovariance(const double *arr, Eigen::MatrixXd &covariance, const std::string &topicName, const std::vector< int > &updateVector, const size_t offset, const size_t dimension)
Utility method for copying covariances from ROS covariance arrays to Eigen matrices.
bool setPoseSrvCallback(robot_localization::SetPose::Request &request, robot_localization::SetPose::Response &)
Service callback for manually setting/resetting the internal pose estimate.
ros::Time latestControlTime_
The time of the most recent control input.
Definition: ros_filter.h:480
TF2SIMD_FORCE_INLINE const Vector3 & getRow(int i) const
tf2Scalar length() const
std::vector< int > loadUpdateConfig(const std::string &topicName)
Loads fusion settings from the config file.
std::vector< ros::Subscriber > topicSubs_
Vector to hold our subscribers until they go out of scope.
Definition: ros_filter.h:488
void add(const std::string &name, TaskFunction f)
ROSCPP_DECL const std::string & getName()
bool printDiagnostics_
Whether or not we print diagnostic messages to the /diagnostics topic.
Definition: ros_filter.h:564
std::string worldFrameId_
tf frame name that is the parent frame of the transform that this node will calculate and broadcast...
Definition: ros_filter.h:652
int staticDiagErrorLevel_
The max (worst) static diagnostic level.
Definition: ros_filter.h:603
void forceTwoD(Eigen::VectorXd &measurement, Eigen::MatrixXd &measurementCovariance, std::vector< int > &updateVector)
Method for zeroing out 3D variables within measurements.
Definition: ros_filter.cpp:286
void clearExpiredHistory(const double cutoffTime)
Removes measurements and filter states older than the given cutoff time.
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
Type const & getType() const
const int POSITION_V_OFFSET
Definition: filter_common.h:78
ros::ServiceServer setPoseSrv_
Service that allows another node to change the current state and recieve a confirmation. Uses a custom SetPose service.
Definition: ros_filter.h:582
#define ROS_FATAL_COND(cond,...)
int dynamicDiagErrorLevel_
The max (worst) dynamic diagnostic level.
Definition: ros_filter.h:447
#define ROS_ERROR_STREAM_DELAYED_THROTTLE(rate, args)
void reset()
Resets the filter to its initial state.
Definition: ros_filter.cpp:100
void setIdentity()
ros::Time lastSetPoseTime_
Store the last time set pose message was received.
Definition: ros_filter.h:519
void controlCallback(const geometry_msgs::Twist::ConstPtr &msg)
Callback method for receiving non-stamped control input.
Definition: ros_filter.cpp:232
double getControlTime()
Returns the time at which the control term was issued.
void enqueueMeasurement(const std::string &topicName, const Eigen::VectorXd &measurement, const Eigen::MatrixXd &measurementCovariance, const std::vector< int > &updateVector, const double mahalanobisThresh, const ros::Time &time)
Adds a measurement to the queue of measurements to be processed.
Definition: ros_filter.cpp:265
bool publishTransform_
Whether we publish the transform from the world_frame to the base_link_frame.
Definition: ros_filter.h:656
#define RF_DEBUG(msg)
Quaternion & normalize()
ROSCPP_DECL CallbackQueue * getGlobalCallbackQueue()
void setRPY(tf2Scalar roll, tf2Scalar pitch, tf2Scalar yaw)
const int ORIENTATION_SIZE
Definition: filter_common.h:87
TransportHints & tcpNoDelay(bool nodelay=true)
std::map< std::string, tf2::Transform > previousMeasurements_
Stores the last measurement from a given topic for differential integration.
Definition: ros_filter.h:551
double getLastMeasurementTime()
Gets the most recent measurement time.
std::string baseLinkOutputFrameId_
tf frame name for the robot&#39;s body frame
Definition: ros_filter.h:421
MeasurementQueue measurementQueue_
We process measurements by queueing them up in callbacks and processing them all at once within each ...
Definition: ros_filter.h:529
boost::shared_ptr< Measurement > MeasurementPtr
Definition: filter_base.h:110
bool disabledAtStartup_
Start the Filter disabled at startup.
Definition: ros_filter.h:634
void imuCallback(const sensor_msgs::Imu::ConstPtr &msg, const std::string &topicName, const CallbackData &poseCallbackData, const CallbackData &twistCallbackData, const CallbackData &accelCallbackData)
Callback method for receiving all IMU messages.
Definition: ros_filter.cpp:410
ros::Duration tfTimeOffset_
For future (or past) dating the world_frame->base_link_frame transform.
Definition: ros_filter.h:615
#define ROS_FATAL_STREAM(args)
Duration & fromSec(double t)
const int POSITION_OFFSET
Definition: filter_common.h:76
TF2SIMD_FORCE_INLINE Vector3 & getOrigin()
ros::ServiceServer enableFilterSrv_
Service that allows another node to enable the filter. Uses a standard Empty service.
Definition: ros_filter.h:590
std::map< std::string, std::string > staticDiagnostics_
This object accumulates static diagnostics, e.g., diagnostics relating to the configuration parameter...
Definition: ros_filter.h:432
#define ROS_WARN_ONCE(...)
#define ROS_INFO(...)
void poseCallback(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg, const CallbackData &callbackData, const std::string &targetFrame, const bool imuData)
Callback method for receiving all pose messages.
bool smoothLaggedData_
Whether or not we use smoothing.
Definition: ros_filter.h:586
void addDiagnostic(const int errLevel, const std::string &topicAndClass, const std::string &message, const bool staticDiag)
Adds a diagnostic message to the accumulating map and updates the error level.
bool searchParam(const std::string &key, std::string &result) const
bool param(const std::string &param_name, T &param_val, const T &default_val) const
ros::Time stamp_
void fromMsg(const A &, B &b)
ROSCPP_DECL bool ok()
bool toggledOn_
$brief Whether the filter should process new measurements or not.
Definition: ros_filter.h:640
static const Transform & getIdentity()
bool enableFilterSrvCallback(std_srvs::Empty::Request &, std_srvs::Empty::Response &)
Service callback for manually enable the filter.
geometry_msgs::TransformStamped worldBaseLinkTransMsg_
Message that contains our latest transform (i.e., state)
Definition: ros_filter.h:648
double gravitationalAcc_
What is the acceleration in Z due to gravity (m/s^2)? Default is +9.80665.
Definition: ros_filter.h:572
#define ROS_WARN_STREAM_DELAYED_THROTTLE(rate, args)
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
const int ACCELERATION_SIZE
Definition: filter_common.h:88
const int POSITION_SIZE
Definition: filter_common.h:86
TF2SIMD_FORCE_INLINE void setOrigin(const Vector3 &origin)
std::string mapFrameId_
tf frame name for the robot&#39;s map (world-fixed) frame
Definition: ros_filter.h:523
#define ROS_WARN_STREAM(args)
TF2SIMD_FORCE_INLINE void mult(const Transform &t1, const Transform &t2)
bool prepareAcceleration(const sensor_msgs::Imu::ConstPtr &msg, const std::string &topicName, const std::string &targetFrame, std::vector< int > &updateVector, Eigen::VectorXd &measurement, Eigen::MatrixXd &measurementCovariance)
Prepares an IMU message&#39;s linear acceleration for integration into the filter.
double clampRotation(double rotation)
Utility method keeping RPY angles in the range [-pi, pi].
OK
void saveFilterState(FilterBase &filter)
Saves the current filter state in the queue of previous filter states.
#define ROS_WARN_STREAM_THROTTLE(rate, args)
void setRotation(const Quaternion &q)
const Eigen::VectorXd & getControl()
Returns the control vector currently being used.
B toMsg(const A &a)
std::string frame_id_
Transform inverseTimes(const Transform &t) const
bool toggleFilterProcessingCallback(robot_localization::ToggleFilterProcessing::Request &, robot_localization::ToggleFilterProcessing::Response &)
Service callback to toggle processing measurements for a standby mode but continuing to publish...
Definition: ros_filter.cpp:130
#define ROS_INFO_STREAM(args)
bool getFilteredOdometryMessage(nav_msgs::Odometry &message)
Retrieves the EKF&#39;s output for broadcasting.
Definition: ros_filter.cpp:316
std::map< std::string, tf2::Transform > initialMeasurements_
Stores the first measurement from each topic for relative measurements.
Definition: ros_filter.h:500
double historyLength_
The depth of the history we track for smoothing/delayed measurement processing.
Definition: ros_filter.h:465
const int POSE_SIZE
Pose and twist messages each contain six variables.
Definition: filter_common.h:84
const int ORIENTATION_OFFSET
Definition: filter_common.h:77
#define ROS_WARN_STREAM_ONCE(args)
bool getParam(const std::string &key, std::string &s) const
bool predictToCurrentTime_
By default, the filter predicts and corrects up to the time of the latest measurement. If this is set to true, the filter does the same, but then also predicts up to the current time step.
Definition: ros_filter.h:560
Matrix3x3 inverse() const
std::map< std::string, std::string > dynamicDiagnostics_
This object accumulates dynamic diagnostics, e.g., diagnostics relating to sensor data...
Definition: ros_filter.h:439
tf2_ros::Buffer tfBuffer_
Transform buffer for managing coordinate transforms.
Definition: ros_filter.h:607
static Time now()
RosFilter(std::vector< double > args=std::vector< double >())
Constructor.
Definition: ros_filter.cpp:50
const Eigen::MatrixXd & getEstimateErrorCovariance()
Gets the estimate error covariance.
bool lookupTransformSafe(const tf2_ros::Buffer &buffer, const std::string &targetFrame, const std::string &sourceFrame, const ros::Time &time, const ros::Duration &timeout, tf2::Transform &targetFrameTrans, const bool silent=false)
Method for safely obtaining transforms.
diagnostic_updater::Updater diagnosticUpdater_
Used for updating the diagnostics.
Definition: ros_filter.h:451
Structure used for storing and comparing filter states.
Definition: filter_base.h:118
bool preparePose(const geometry_msgs::PoseWithCovarianceStamped::ConstPtr &msg, const std::string &topicName, const std::string &targetFrame, const bool differential, const bool relative, const bool imuData, std::vector< int > &updateVector, Eigen::VectorXd &measurement, Eigen::MatrixXd &measurementCovariance)
Prepares a pose message for integration into the filter.
bool twoDMode_
Whether or not we&#39;re in 2D mode.
Definition: ros_filter.h:623
void add(const std::string &key, const T &val)
bool prepareTwist(const geometry_msgs::TwistWithCovarianceStamped::ConstPtr &msg, const std::string &topicName, const std::string &targetFrame, std::vector< int > &updateVector, Eigen::VectorXd &measurement, Eigen::MatrixXd &measurementCovariance)
Prepares a twist message for integration into the filter.
void clearMeasurementQueue()
Clears measurement queue.
std::string odomFrameId_
tf frame name for the robot&#39;s odometry (world-fixed) frame
Definition: ros_filter.h:541
bool hasParam(const std::string &key) const
#define ROS_ERROR_STREAM(args)
double frequency_
The frequency of the run loop.
Definition: ros_filter.h:459
#define ROS_ASSERT(cond)
std::ofstream debugStream_
Used for outputting debug messages.
Definition: ros_filter.h:443
std::vector< std::string > stateVariableNames_
Contains the state vector variable names in string format.
Definition: ros_filter.h:599
ROSCPP_DECL void spinOnce()
static bool waitForValid()
ros::NodeHandle nh_
Node handle.
Definition: ros_filter.h:533
void quatToRPY(const tf2::Quaternion &quat, double &roll, double &pitch, double &yaw)
Utility method for converting quaternion to RPY.
void run()
Main run method.
T filter_
Our filter (EKF, UKF, etc.)
Definition: ros_filter.h:455
void setData(const T &input)
bool revertTo(const double time)
Finds the latest filter state before the given timestamp and makes it the current state again...
static bool isSimTime()
FilterStateHistoryDeque filterStateHistory_
An implicitly time ordered queue of past filter states used for smoothing.
Definition: ros_filter.h:666
StateMembers
Enumeration that defines the state vector.
Definition: filter_common.h:41
const int ORIENTATION_V_OFFSET
Definition: filter_common.h:79
std::map< std::string, bool > removeGravitationalAcc_
If including acceleration for each IMU input, whether or not we remove acceleration due to gravity...
Definition: ros_filter.h:568


robot_localization
Author(s): Tom Moore
autogenerated on Wed Feb 3 2021 03:34:02