-
Notifications
You must be signed in to change notification settings - Fork 55
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
feat: add ring based filter for velodyne #69
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## main #69 +/- ##
========================================
+ Coverage 4.84% 9.21% +4.37%
========================================
Files 249 78 -171
Lines 19210 9384 -9826
Branches 1075 1060 -15
========================================
- Hits 931 865 -66
+ Misses 17579 7820 -9759
+ Partials 700 699 -1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
This looks like an interesting feature. |
7c0c855
to
1c63a05
Compare
Hello @amc-nu , do you mean a new composable node inside Nebula or inside Autoware sensing pipeline? My idea was to implement it inside the driver so that the delay will be lower. |
1c63a05
to
5cdce1a
Compare
78ee135
to
77a4781
Compare
Hello @drwnz , this PR is ready for a review. |
deaea10
to
6154ce8
Compare
6154ce8
to
0ae17b5
Compare
README.md
Outdated
| cloud_min_angle | uint16 | 0 | degrees [0, 360] | FoV start angle | | ||
| cloud_max_angle | uint16 | 359 | degrees [0, 360] | FoV end angle | | ||
| invalid_point_remove | bool | false | true, false | Enable ring based filter* | | ||
| invalid_regions | string | | | Invalid point regions to remove* | |
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.
| invalid_regions | string | | | Invalid point regions to remove* | | |
| excluded_ring_sectors | string | | | Identifies and prevents specific ring sectors from being included in the point cloud based on ring ID, start angle, and end angle. | |
README.md
Outdated
*`invalid_point_remove` activates the ring based filter which removes points if they are within specified angle ranges. | ||
|
||
*The format for an invalid region is [ring_id, start_angle, end_angle] | ||
|
||
*Angles are given in degrees and multiplied by 100. For instance, 34.44 degrees is represented as 3444. | ||
|
||
*Invalid regions are specified as a string containing a list of invalid regions. Ensure that you have quotation marks to make it string. For example: |
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.
*`invalid_point_remove` activates the ring based filter which removes points if they are within specified angle ranges. | |
*The format for an invalid region is [ring_id, start_angle, end_angle] | |
*Angles are given in degrees and multiplied by 100. For instance, 34.44 degrees is represented as 3444. | |
*Invalid regions are specified as a string containing a list of invalid regions. Ensure that you have quotation marks to make it string. For example: | |
- `enable_ring_section_filter` toggles a ring-based filter to remove points located within predefined angle ranges. | |
- Specify an excluded section using the format `[ring_id, start_angle, end_angle]`. | |
- Angles must be in degrees, scaled by a factor of 100. For example, represent `34.44` degrees as `3444`. | |
- It's possible to define multiple excluded regions for the same ring, allowing for versatile filtering configurations. | |
- Define excluded regions as a string containing a list of such regions, enclosed in quotation marks. For instance: |
README.md
Outdated
|
||
*Multiple invalid regions are possible for the same ring. |
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.
*Multiple invalid regions are possible for the same ring. |
README.md
Outdated
name="velodyne_cloud" output="screen"> | ||
... | ||
<param name="invalid_point_remove" value="true"/> | ||
<param name="invalid_regions" value="'[[0, 3500, 6900], [1, 3400, 6500], [2, 3200, 4600], [3, 3200, 4600]]'"/> |
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.
<param name="invalid_regions" value="'[[0, 3500, 6900], [1, 3400, 6500], [2, 3200, 4600], [3, 3200, 4600]]'"/> | |
<param name="invalid_regions" value="'[[0, 3500, 6900], [1, 3400, 6500], [2, 3200, 4600], [3, 3200, 4600]]'"/> |
Why do we need the additional single quotes?
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.
Hello, since ROS 2 doesn't support setting parameters as a list of lists, Boost Property Tree library has been used.
invalid_regions
is assumed as a string and parsed with the library. To make it string, I have added another set of quotes.
#include "boost/property_tree/json_parser.hpp" | ||
#include "boost/property_tree/ptree.hpp" |
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.
#include "boost/property_tree/json_parser.hpp" | |
#include "boost/property_tree/ptree.hpp" | |
#include <nlohmann/json.hpp> |
Let's use this instead, boost is too heavy for this task.
<depend>nlohmann-json-dev</depend>
can be added to the package.xml
file and all should work fine.
And the rest of the code will be much cleaner and also will be error checked:
// Assuming `nlohmann::json` namespace is available as `json`
void parseInvalidRegions(const std::string& regions_json) {
// Parse the JSON string
auto invalid_regions_json = json::parse(regions_json);
// Clear existing regions
sensor_configuration_.invalid_regions.clear();
// Iterate over the parsed JSON array
for (const auto& region : invalid_regions_json) {
// Extract the ring number, start angle, and end angle from each sub-array
int ring_number = region[0];
uint16_t start = region[1];
uint16_t end = region[2];
// Add the InvalidRegion to the map under the corresponding ring number
sensor_configuration_.invalid_regions[ring_number].emplace_back(InvalidRegion{start, end});
}
}
99d5413
to
847c1a7
Compare
Hello @xmfcx , I updated the PR. Could you please have another look? |
a47d4a7
to
96f1c4a
Compare
Signed-off-by: Mehmet Emin BAŞOĞLU <[email protected]>
96f1c4a
to
860f6a0
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.
Thank you for the PR, and apologies for the delay in review!
We would really like to apply this methodology to Hesai decoders as well, so if you can look at the suggested changes, we will proceed quickly to review. Mostly, if we are to adopt throughout multiple sensor manufacturers and models, we need to make sure to minimize the performance cost.
@@ -182,6 +182,19 @@ class VelodyneScanDecoder | |||
virtual void reset_pointcloud(size_t n_pts, double time_stamp) = 0; | |||
/// @brief Resetting overflowed point cloud buffer | |||
virtual void reset_overflow(double time_stamp) = 0; | |||
|
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 check has to be calculated with a search in the excluded regions parameters for every point, which is causing the performance overhead. We suggest pre-computing the binary value for each azimuth and channel value to make a map, allowing a single conditional to check if the point is valid or not.
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.
@mebasoglu Thank you for your contribution!
I'm sorry for responding that late 🙇 but here is my review.
The feature itself looks great and is a nice addition to Nebula but I would like you to refactor the filter code into its own isolated class as much as possible to properly separate the sensor features from Nebula features. Please respond to the comments I left above.
Thank you!
|
||
- `enable_ring_section_filter` toggles a ring-based filter to remove points located within predefined angle ranges. | ||
- Specify an excluded section using the format `[ring_id, start_angle, end_angle]`. | ||
- Angles must be in degrees, scaled by a factor of 100. For example, represent `34.44` degrees as `3444`. |
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.
In my opinion, it would be more user-friendly to have a float here (34.44) and for Nebula to convert it to an int 3444 internally.
/// @brief Invalid region on the cloud to be removed. `start` and `end` represent angles of the | ||
/// region. | ||
struct ExcludedRegion | ||
{ | ||
uint16_t start; | ||
uint16_t end; | ||
}; |
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.
As the filter is not inherently a feature of the sensor, please move it to a class RingSectionFilter in its own header file.
bool ring_section_filter; | ||
std::map<int, std::vector<ExcludedRegion>> | ||
excluded_ring_sectors; // Key holds the channel id, value holds excluded ring sectors belong to that | ||
// channel |
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.
As it is now, the bool
and std::map
could be assigned in invalid ways, e.g. ring_section_filter = false; excluded_ring_sectors = { /* not empty */ };
. We should avoid such possibilities as much as possibe to make Nebula safer and more maintainable.
This would be cleaner as std::optional<RingSectionFilter>
.
/// @brief Checks if the point is inside invalid regions. | ||
/// @param channel Channel id of the point. | ||
/// @param azimuth Azimuth angle of the point. | ||
/// @return True if the point is invalid, false otherwise. | ||
bool check_excluded_point(const int & channel, const uint16_t & azimuth) | ||
{ | ||
if (!sensor_configuration_->ring_section_filter) return false; | ||
const auto & sectors = sensor_configuration_->excluded_ring_sectors[channel]; | ||
return std::any_of(sectors.begin(), sectors.end(), [azimuth](const auto & sector) { | ||
return azimuth >= sector.start && azimuth <= sector.end; | ||
}); | ||
} |
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.
since the filter is not an inherent feature of the sensor, please move this to a class RingSectionFilter
(and as @drwnz mentioned, precompute in the class's constructor).
const bool is_not_excluded_point = | ||
!check_excluded_point(corrections.laser_ring, azimuth_corrected); | ||
|
||
if ((is_within_min_max_angle || is_outside_max_min_angle) && is_not_excluded_point) { |
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.
To make clear to the reader that the existing FOV filtering (min_angle
and max_angle
) and the new RingBasedFilter
are separate features, please refactor to:
const bool is_not_excluded_point = | |
!check_excluded_point(corrections.laser_ring, azimuth_corrected); | |
if ((is_within_min_max_angle || is_outside_max_min_angle) && is_not_excluded_point) { | |
const bool is_excluded_point = | |
check_excluded_point(corrections.laser_ring, azimuth_corrected); | |
if (is_excluded_point) { | |
continue; | |
} | |
if (is_within_min_max_angle || is_outside_max_min_angle) { |
const bool is_not_excluded_point = | ||
!check_excluded_point(corrections.laser_ring, block.rotation); | ||
|
||
if ((is_within_min_max_angle || is_outside_max_min_angle) && is_not_excluded_point) { |
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.
To make clear to the reader that the existing FOV filtering (min_angle
and max_angle
) and the new RingBasedFilter
are separate features, please refactor to:
const bool is_not_excluded_point = | |
!check_excluded_point(corrections.laser_ring, block.rotation); | |
if ((is_within_min_max_angle || is_outside_max_min_angle) && is_not_excluded_point) { | |
const bool is_excluded_point = | |
check_excluded_point(corrections.laser_ring, block.rotation); | |
if (is_excluded_point) { | |
continue; | |
} | |
if (is_within_min_max_angle || is_outside_max_min_angle) { |
const bool is_not_excluded_point = | ||
!check_excluded_point(corrections.laser_ring, azimuth_corrected); | ||
|
||
if ((is_within_min_max_angle || is_outside_max_min_angle) && is_not_excluded_point) { |
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.
To make clear to the reader that the existing FOV filtering (min_angle
and max_angle
) and the new RingBasedFilter
are separate features, please refactor to:
const bool is_not_excluded_point = | |
!check_excluded_point(corrections.laser_ring, azimuth_corrected); | |
if ((is_within_min_max_angle || is_outside_max_min_angle) && is_not_excluded_point) { | |
const bool is_excluded_point = | |
check_excluded_point(corrections.laser_ring, azimuth_corrected); | |
if (is_excluded_point) { | |
continue; | |
} | |
if (is_within_min_max_angle || is_outside_max_min_angle) { |
@@ -19,13 +19,19 @@ | |||
|
|||
<arg name="setup_sensor" default="True" description="Enable sensor setup on hw-driver."/> | |||
|
|||
<arg name="enable_ring_section_filter" default="false"/> | |||
<arg name="excluded_ring_sectors" | |||
default="[0, 3500, 6900], [1, 3400, 6500], [2, 3200, 4600], [3, 3200, 4600]"/> |
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.
As noted in README.md
, I think specifying floats (35.00, 69.00 etc.) is more easily understandable to the user.
Also, please set the default to an emty filter. Instead, a `description=". Example: '[0, 3500, 6900], ...'" would be nice.
// Add the ExcludedRegion to the map under the corresponding ring number | ||
sensor_configuration.excluded_ring_sectors[ring_number].emplace_back( | ||
drivers::ExcludedRegion{start, end}); |
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.
As noted in velodyne_common.hpp
, it would be nicer (better separation of concerns) if the configuration contained a std::optional<RingBasedFilter>
instead of bool ring_section_filter
and excluded_ring_sectors
.
- `enable_ring_section_filter` toggles a ring-based filter to remove points located within predefined angle ranges. | ||
- Specify an excluded section using the format `[ring_id, start_angle, end_angle]`. | ||
- Angles must be in degrees, scaled by a factor of 100. For example, represent `34.44` degrees as `3444`. | ||
- It's possible to define multiple excluded regions for the same ring, allowing for versatile filtering configurations. |
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.
"It's" sounds a bit informal, how about:
- It's possible to define multiple excluded regions for the same ring, allowing for versatile filtering configurations. | |
- It is possible to define multiple excluded regions for the same ring, allowing for versatile filtering configurations. |
@mebasoglu As we want to implement something similar for Hesai sensors too, I think we can pick up where you left off. Not sure if it will happen this week, but definitely soon! |
PR Type
Related Links
The issue: #60
Description
This is an implementation for a ring based filter. It removes points if they are within specified angle ranges.
Review Procedure
There are two new parameters:
enable_ring_section_filter
andexcluded_ring_sectors
onvelodyne_decoder_ros_wrapper.cpp
.enable_ring_section_filter
is a boolean to activate the filter.excluded_ring_sectors
is a string which holds a list of excluded sectors.[12, 31110, 31495], [], []
An excluded region is a list which holds ring, start angle and end angle.
[12, 31110, 31495]
Since ROS 2 doesn't support setting parameters as a list of lists,
nlohmann-json-dev
library has been used.excluded_ring_sectors
string is parsed using the library.On
nebula_decoders/include/nebula_decoders/nebula_decoders_velodyne/decoders/velodyne_scan_decoder.hpp
file a new function has been added
check_excluded_point
.This function takes channel id and azimuth angle of the point and checks if the point inside any of the regions specified
for that ring.
The function was used in
vlp16_decoder.hpp
,vlp32_decoder.hpp
andvlp128_decoder.hpp
.A sample usage has been added to
velodyne_launch_all_hw.xml
.Here is a sample bag file which has only
/velodyne_packets
of aVLP16
to test: https://drive.google.com/drive/folders/1LY_Nyt5Z_M6rG4juSB1UiKk9IFBKVxU7?usp=sharingThese parameters were used for the bag:
Remarks
Comparison between
main
branch and this PR:Without the filter, we have reflected points which are under the ground:
With the filter:
Pre-Review Checklist for the PR Author
PR Author should check the checkboxes below when creating the PR.
Checklist for the PR Reviewer
Reviewers should check the checkboxes below before approval.
Post-Review Checklist for the PR Author
PR Author should check the checkboxes below before merging.
CI Checks