Skip to content

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d21cfcc
Make member variables of LowPassFilter class generic
pedroazeredo04 Apr 27, 2025
1612ade
Run pre-commit and format code
pedroazeredo04 Apr 27, 2025
72a80ba
Add filter traits structure
pedroazeredo04 Apr 27, 2025
68a550d
Make LowPassFilter::update() function generic
pedroazeredo04 Apr 28, 2025
5402d8d
Remove useless comments
pedroazeredo04 Apr 28, 2025
a21b6a2
Fix pre commit warning
pedroazeredo04 Apr 28, 2025
82da940
Remove useless function
pedroazeredo04 Apr 28, 2025
8a3d6e6
Merge branch 'ros2-master' into improve_low_pass_filter_class
christophfroehlich Apr 28, 2025
45f3c74
Implement generic Traits::add_metadata method
pedroazeredo04 Apr 30, 2025
85b46c4
Rename Traits::is_infinite method to Traits::is_finite
pedroazeredo04 Apr 30, 2025
a1710db
Change affilliation in FilterTraits header
pedroazeredo04 Apr 30, 2025
dd0e3db
Change Vector6d to Eigen::Matrix in FilterTraits
pedroazeredo04 Apr 30, 2025
e6a066e
Fix eigen matrix initialization as NaN
pedroazeredo04 May 1, 2025
00c0ffa
Add is_std_vector method
pedroazeredo04 May 2, 2025
60c132a
Add generic validate_input method
pedroazeredo04 May 2, 2025
ab695fb
Remove unused is_std_vector helper
pedroazeredo04 May 3, 2025
56c7edb
Generalize types inside the std::vector overload of FilterTraits
pedroazeredo04 May 3, 2025
9156cb0
Remove allocator specialization
pedroazeredo04 May 4, 2025
f68ad4a
Merge branch 'ros2-master' into improve_low_pass_filter_class
saikishor May 4, 2025
4a7609a
Initialize std::vector as empty inside filter traits
pedroazeredo04 May 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 243 additions & 0 deletions control_toolbox/include/control_toolbox/filter_traits.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
// Copyright (c) 2025, ros2_control development team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef CONTROL_TOOLBOX__FILTER_TRAITS_HPP_
#define CONTROL_TOOLBOX__FILTER_TRAITS_HPP_

#define EIGEN_INITIALIZE_MATRICES_BY_NAN

#include <Eigen/Dense>
#include <algorithm>
#include <cmath>
#include <limits>
#include <vector>

#include "geometry_msgs/msg/wrench_stamped.hpp"

namespace control_toolbox
{

template <typename T>
struct FilterTraits;

// Wrapper around std::vector<U> to be used as
// the std::vector<U> StorageType specialization.
// This is a workaround for the fact that
// std::vector<U>'s operator* and operator+ cannot be overloaded.
template <typename U>
struct FilterVector
{
std::vector<U> data;

FilterVector() = default;

explicit FilterVector(const std::vector<U> & vec) : data(vec) {}

explicit FilterVector(size_t size, const U & initial_value = U{}) : data(size, initial_value) {}

FilterVector operator*(const U & scalar) const
{
FilterVector result = *this;
for (auto & val : result.data)
{
val *= scalar;
}
return result;
}

FilterVector operator+(const FilterVector & other) const
{
assert(data.size() == other.data.size() && "Vectors must be of the same size for addition");
FilterVector result = *this;
for (size_t i = 0; i < data.size(); ++i)
{
result.data[i] += other.data[i];
}
return result;
}

size_t size() const { return data.size(); }
};

// Enable scalar * FilterVector
template <typename U>
inline FilterVector<U> operator*(const U & scalar, const FilterVector<U> & vec)
{
return vec * scalar;
}

template <typename T>
struct FilterTraits
{
using StorageType = T;

static void initialize(StorageType & storage)
{
storage = T{std::numeric_limits<T>::quiet_NaN()};
}

static bool is_nan(const StorageType & storage) { return std::isnan(storage); }

static bool is_finite(const StorageType & storage) { return std::isfinite(storage); }

static bool is_empty(const StorageType & storage)
{
(void)storage;
return false;
}

static void assign(StorageType & storage, const StorageType & data_in) { storage = data_in; }

static void validate_input(const T & data_in, const StorageType & filtered_value, T & data_out)
{
(void)data_in;
(void)filtered_value;
(void)data_out; // Suppress unused warnings
}

static void add_metadata(StorageType & storage, const StorageType & data_in)
{
(void)storage;
(void)data_in;
}
Comment on lines +102 to +113
Copy link
Member

Choose a reason for hiding this comment

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

Can't we make it pure virtual?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we can't do virtual templates (I say this based on this discussion here). But the discussion does mention type erasure as an alternative, which might be applicable in our case. What do you think?

};

template <>
struct FilterTraits<geometry_msgs::msg::WrenchStamped>
{
using StorageType = Eigen::Matrix<double, 6, 1>;
using DataType = geometry_msgs::msg::WrenchStamped;

static void initialize(StorageType & storage)
{
// Evocation of the default constructor through EIGEN_INITIALIZE_MATRICES_BY_NAN
storage = StorageType();
}

static bool is_nan(const StorageType & storage) { return storage.hasNaN(); }

static bool is_finite(const DataType & data)
{
return std::isfinite(data.wrench.force.x) && std::isfinite(data.wrench.force.y) &&
std::isfinite(data.wrench.force.z) && std::isfinite(data.wrench.torque.x) &&
std::isfinite(data.wrench.torque.y) && std::isfinite(data.wrench.torque.z);
}

static bool is_empty(const StorageType & storage)
{
(void)storage;
return false;
}

static void assign(DataType & data_in, const StorageType & storage)
{
data_in.wrench.force.x = storage[0];
data_in.wrench.force.y = storage[1];
data_in.wrench.force.z = storage[2];
data_in.wrench.torque.x = storage[3];
data_in.wrench.torque.y = storage[4];
data_in.wrench.torque.z = storage[5];
}

static void assign(StorageType & storage, const DataType & data_in)
{
storage[0] = data_in.wrench.force.x;
storage[1] = data_in.wrench.force.y;
storage[2] = data_in.wrench.force.z;
storage[3] = data_in.wrench.torque.x;
storage[4] = data_in.wrench.torque.y;
storage[5] = data_in.wrench.torque.z;
}

static void assign(StorageType & storage, const StorageType & data_in) { storage = data_in; }

static void validate_input(
const DataType & data_in, const StorageType & filtered_value, DataType & data_out)
{
(void)filtered_value; // Not used here

// Compare new input's frame_id with previous output's frame_id
assert(
data_in.header.frame_id == data_out.header.frame_id &&
"Frame ID changed between filter updates");
}

static void add_metadata(DataType & data_out, const DataType & data_in)
{
data_out.header = data_in.header;
}
};

template <typename U>
struct FilterTraits<std::vector<U>>
{
using StorageType = FilterVector<U>;
using DataType = std::vector<U>;

static void initialize(StorageType & storage) { (void)storage; }

static bool is_finite(const StorageType & storage)
{
return std::all_of(
storage.data.begin(), storage.data.end(), [](U val) { return std::isfinite(val); });
}

static bool is_finite(const DataType & storage)
{
return std::all_of(storage.begin(), storage.end(), [](U val) { return std::isfinite(val); });
}

static bool is_empty(const StorageType & storage) { return storage.data.empty(); }

static bool is_nan(const StorageType & storage)
{
for (const auto & val : storage.data)
{
if (std::isnan(val))
{
return true;
}
}

return false;
}

static void assign(StorageType & storage, const DataType & data_in) { storage.data = data_in; }

static void assign(DataType & storage, const StorageType & data_in) { storage = data_in.data; }

static void assign(StorageType & storage, const StorageType & data_in)
{
storage.data = data_in.data;
}

static void validate_input(
const DataType & data_in, const StorageType & filtered_value, DataType & data_out)
{
assert(
data_in.size() == filtered_value.size() &&
"Input vector size does not match internal state size");
assert(data_out.size() == data_in.size() && "Input and output vectors must be the same size");
}

static void add_metadata(DataType & storage, const DataType & data_in)
{
(void)storage;
(void)data_in;
}
};

} // namespace control_toolbox

#endif // CONTROL_TOOLBOX__FILTER_TRAITS_HPP_
Loading
Loading