-
Notifications
You must be signed in to change notification settings - Fork 105
Make member variables of LowPassFilter class generic #351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ros2-master
Are you sure you want to change the base?
Make member variables of LowPassFilter class generic #351
Conversation
@pedroazeredo04, all pull requests must be targeted towards the |
Signed-off-by: Pedro Nogueira <pedro_azeredo@usp.br>
This pull request is in conflict. Could you fix it @pedroazeredo04? |
e817778
to
d21cfcc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pedroazeredo04 can you fix the pre-commit?. Just install pre-commit and run pre-commit run --all-files
|
||
// Define the storage type based on T | ||
using StorageType = typename std::conditional< | ||
std::is_same<T, geometry_msgs::msg::WrenchStamped>::value, Eigen::Matrix<double, 6, 1>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::is_same<T, geometry_msgs::msg::WrenchStamped>::value, Eigen::Matrix<double, 6, 1>, | |
std::is_same<T, geometry_msgs::msg::WrenchStamped>::value, Eigen::Vector6d, |
May be we could use this directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might come from
https://github.com/ros/geometry/blob/noetic-devel/eigen_conversions/include/eigen_conversions/eigen_msg.h#L100
according to the comment
// TODO(destogl): use wrenchMsgToEigen
But I don't find these conversion wrappers for ROS 2, https://docs.ros.org/en/rolling/p/tf2_eigen/generated/index.html#tf2-eigen
Thanks a lot for your contribution.
You are right about that, I made a proposal in the issue: what do you think? I still think that the algorithms in the filters would be more clean, and we easily could apply that to all the other filters and add for example Pose messages (in a future PR). |
@christophfroehlich That sounds nice! I didn't know we could use this logic in other filters. I will leave this implementation for a future PR, but in which filters could we also use this? Also, do you think I should create a new file (for instance |
Signed-off-by: Pedro Nogueira <pedro_azeredo@usp.br>
for now, the exponential filter would be the only other candidate. But more might come in the future.
yes, exactly. |
Signed-off-by: Pedro Nogueira <pedro_azeredo@usp.br>
@christophfroehlich I commited your suggestion of having a That is because the override of this function for the type if (filtered_value_.empty())
{
if (std::any_of(data_in.begin(), data_in.end(), [](double val) { return !std::isfinite(val); }))
{
return false;
}
filtered_value_ = data_in;
filtered_old_value_ = data_in;
old_value_ = data_in;
}
else
{
assert(
data_in.size() == filtered_value_.size() &&
"Internal data and the data_in doesn't hold the same size");
assert(data_out.size() == data_in.size() && "data_in and data_out doesn't hold same size");
} is very distinct from the other overrides, so it is hard to generalize this for every data type. Also, about the other methods of the
I would only have to say , in order to do an operator overload for // undefined behavior
template<typename T>
std::vector<T> operator+(const std::vector<T>& lhs, const std::vector<T>& rhs) {
std::vector<T> result(lhs.size());
for (size_t i = 0; i < lhs.size(); ++i) {
result[i] = lhs[i] + rhs[i];
}
return result;
} We would probably have to do // Wrapper around std::vector<double> to enable element-wise + and * without touching std::
struct VecD {
std::vector<double> v;
VecD() = default;
VecD(const std::vector<double>& data) : v(data) {}
bool empty() const { return v.empty(); }
void resize(size_t n) { v.resize(n); }
double& operator[](size_t i) { return v[i]; }
const double& operator[](size_t i) const { return v[i]; }
};
// element-wise add
inline VecD operator+(const VecD& a, const VecD& b) {
VecD out(a.v.size());
for (size_t i = 0; i < a.v.size(); ++i)
out.v[i] = a.v[i] + b.v[i];
return out;
} |
Thanks for your work on this. My suggestion was only a draft, I'm not really experienced in writing templated classes, so I might be running in a dead end. Please shout out if this is the case ;)
The if branch could be implemented with the already mentionend isempty(), isnan(), and the else branch could be solved by a // helper: default false
template<typename T>
struct is_std_vector : std::false_type {};
// specialization for std::vector
template<typename U, typename Alloc>
struct is_std_vector<std::vector<U, Alloc>> : std::true_type {}; and use
This is a very good hint (haven't thought about that), but we aren't overloading this to |
Signed-off-by: Pedro Nogueira <pedro_azeredo@usp.br>
Hi @christophfroehlich ! No need to thank my work on this, I'm having fun messing around with templates :) I pushed a whip code that tries to make the update() function trully generic.
That is a good idea! I did something similar, but checking for the types inside of the function itself.
In my understanding, the problem is that filtered_value_ = old_value_ * b1_ + filtered_old_value_ * a1_;
filtered_old_value_ = filtered_value_; work for every possible datatype, I had to add in a |
Signed-off-by: Pedro Nogueira <pedro_azeredo@usp.br>
Signed-off-by: Pedro Nogueira <pedro_azeredo@usp.br>
Signed-off-by: Pedro Nogueira <pedro_azeredo@usp.br>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the direction of the simplification of the filter itself. The filter_traits look a bit verbose, but the API is clean and understandable IMHO.
The tests are failing now, apart from that only some minor comments from my side are left.
@@ -0,0 +1,198 @@ | |||
// Copyright (c) 2023, Stogl Robotics Consulting UG (haftungsbeschränkt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Copyright (c) 2023, Stogl Robotics Consulting UG (haftungsbeschränkt) | |
// Copyright (c) 2025, ros2_control development team |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or you can just add your affiliation
data_out[i] = b1_ * old_value[i] + a1_ * filtered_old_value[i]; | ||
filtered_old_value[i] = data_out[i]; | ||
if (std::isfinite(data_in[i])) | ||
if constexpr (std::is_same_v<T, std::vector<double>>) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my suggestion of is_std_vector
would have been working with any std::vector<T>
, but not sure if any other datatype than double will ever make sense here 😄
if constexpr (std::is_same_v<T, geometry_msgs::msg::WrenchStamped>) | ||
{ | ||
old_value = data_in; | ||
data_out.header = data_in.header; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this could be generalized with something like add_metadata
, because we'll need this for any message type then?
|
||
static bool is_nan(const StorageType & storage) { return std::isnan(storage); } | ||
|
||
static bool is_infinite(const StorageType & storage) { return !std::isfinite(storage); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd prefer to use the is_finite
instead of is_infinite
, as we are used to this from std
template <> | ||
struct FilterTraits<geometry_msgs::msg::WrenchStamped> | ||
{ | ||
using StorageType = Eigen::Matrix<double, 6, 1>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we use?
using StorageType = Eigen::Matrix<double, 6, 1>; | |
using StorageType = Vector6d; |
This PR aims to tackle #345.
In order to do so, I followed the advice from @christophfroehlich, using
type_traits
to identify which type was passed to the template of theLowPassFilter
and configure theStorageType
accordingly, all of that in compile time.Since there is now only one set of three variables instead of two sets, I also corrected all the other occurrences of the variables were removed.
Also, @christophfroehlich said
I thought this was a very beautiful idea, but I had some difficulties making this actually happen, because the function
update()
has some particularities for each data type. At the end, I was basically re-implementing theupdate()
function three times within the traits struct. So I thought it was simpler and more readable to leave the overload the way it is. However, I am open to suggestions!!One last thing: I had to change the base branch to
jazzy
, because theros2-master
branch was not compiling in my machine. Would you like for me to target the branchros2-master
instead ofjazzy
? EDIT: The bot told me to do it, so I did it.Please let me know anything that would be nice to change