Skip to content
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

refactor(velodyne): implement generic velodyne decoder #138

Closed
wants to merge 18 commits into from

Conversation

bgilby59
Copy link
Contributor

@bgilby59 bgilby59 commented Apr 16, 2024

PR Type

  • Improvement

Related Links

Description

Previously, there was a separate Velodyne decoder for each sensor. However, since the sensors function very similarly, this leads to large amounts of repeated code that is unnecessary and difficult to maintain.

This PR implements a generic decoder to handle all Velodyne sensors.

Design

VelodyneDecoder<SensorT>

Generic decoder using templating to get sensor-specific information

VLP16

Class holding VLP16 specific information

VLS128

Class holding VLS128 specific information

Review Procedure

  1. colcon test --event-handlers console_cohesion+ --packages-above nebula_common
  2. code review

Remarks

Current Progress:

Task Done? Notes
Add VLP16 and VLS128 support to generic decoder
Add VLP32 support to generic decoder Tests are broken, see below.
General refactor
Validate on data live
Evaluate performance

Pre-Review Checklist for the PR Author

PR Author should check the checkboxes below when creating the PR.

  • Assign PR to reviewer

Checklist for the PR Reviewer

Reviewers should check the checkboxes below before approval.

  • Commits are properly organized and messages are according to the guideline
  • (Optional) Unit tests have been written for new behavior
  • PR title describes the changes

Post-Review Checklist for the PR Author

PR Author should check the checkboxes below before merging.

  • All open points are addressed and tracked via issues or tickets

CI Checks

  • Build and test for PR: Required to pass before the merge.

@mojomex mojomex self-requested a review April 23, 2024 09:05
@bgilby59
Copy link
Contributor Author

bgilby59 commented Apr 26, 2024

Progress Update:

First draft of generic decoder is finished! (It still needs some refactoring to get rid of compiler warnings and make the code more readable, but functionality is complete, so I think it can be reviewed).

All tests are broken, but for explainable reasons:

1. VLP16

get_pointcloud() was incorrect. The fix in 4b02317 corrects get_pointcloud but causes VLP16 decoder test to fail.

2. VLP32

The number of points differ because ROI selection is limited by both azimuth and azimuth_corrected instead of just azimuth as it was before.

Other point differences appear to be small offsets based on precision difference or comparison of incomparable points arising from the difference in number of points (all floats were made doubles in 0195ef0, and azimuth calculation uses a different method than before (following page 68 in the VLP32 User Manual. This is the method that was used for the current VLP16 and VLS128 decoders)).

3. VLS128

Point differences are small offsets coming from changing floats to doubles in 0195ef0.

Visual results of current decoder (red) and new generic decoder (white) on test data:

vlp16_before_and_after.webm
vlp32_before_and_after.webm
vls128_before_and_after.webm

@bgilby59 bgilby59 marked this pull request as ready for review April 26, 2024 08:28
Copy link
Collaborator

@mojomex mojomex left a comment

Choose a reason for hiding this comment

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

Hey, thank you for your great and very fast work!

There are still a few points I'd like to address before merging, most of which are just refactorings, which by the looks of it, you still want to do anyway (it's not yet marked ✅ in your table above).

There are a few relics from before your refactoring that I would like you to address while you're at it, mainly having to do with parsing and type safety. I left comments in all the relevant places.

I will do a functional test once the comments are addressed.
Thanks!

Copy link
Collaborator

Choose a reason for hiding this comment

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

This file is currently empty. Please either refactor code into here or delete this file.

Comment on lines +31 to +33
static const int RAW_SCAN_SIZE = 3; // TODO: remove
static const int RAW_CHANNEL_SIZE = 3;
static const int SCANS_PER_BLOCK = 32; // TODO: remove
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please address these TODOs

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please move all sensor-specific constants to the respective sensor's file. For constants common to all sensors, move them to a VelodyneSensor class that all sensors intherit from. Ideally the decoder does not contain any constants that do not inherently apply to all sensors.

Copy link
Collaborator

Choose a reason for hiding this comment

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

All the structs (raw_block etc.) would ideally be moved to velodyne_packet.hpp.
Also, make sure all structs that are parsed using memcpy or (not recommended: reinterpret_cast and C-style casts) are defined between

#pragma pack(push, 1)

// define all structs here

#pragma pack(pop)

so that memory layout without padding is guaranteed.


constexpr static double single_firing_s = 2.304 * 1e-6;

constexpr static double offset_packet_time = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

These constants should ideally be implemented as arguments to the parent class (VelodyneSensor) constructor or template.
As it is, there is no guarantee that each child class defines all the constants accessed by the decoder (a new developer implementing a new sensor might not realize they have to define those constants for the decoder to work).
If these constants need to be passed to a constructor, there is no way that anyone forgets to put them here.


constexpr static double single_firing_s = 2.304 * 1e-6;

constexpr static double offset_packet_time = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

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

See comment on VLP16

default:
// Do not flood the log with messages, only issue at most one
// of these warnings per minute.
return; // bad packet: skip the rest
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please do some error handling here, at least RCLCPP_ERROR_THROTTLE() or something to let the user know something is wrong.

}

// Condition added to avoid calculating points which are not in the interesting defined area
// (cloud_min_angle < area < cloud_max_angle).
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please change this comment to <= area <=

azimuth >= sensor_configuration_->cloud_min_angle * 100))) {
for (int firing_seq = 0, k = 0;
firing_seq <
std::max(static_cast<long>(SensorT::firing_sequences_per_block), static_cast<long>(1));
Copy link
Collaborator

Choose a reason for hiding this comment

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

You are casting to long but firing_seq is an int, so passing int is fine.

azimuth <= sensor_configuration_->cloud_max_angle * 100) ||
(sensor_configuration_->cloud_min_angle > sensor_configuration_->cloud_max_angle &&
(azimuth <= sensor_configuration_->cloud_max_angle * 100 ||
azimuth >= sensor_configuration_->cloud_min_angle * 100))) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

For all if-statements that filter out points, please convert them to guard clauses:
Instead of

if (conditionA) {
  // ...
  if (conditionB) {
    // decode point
  }
}

invert the conditions and skip the point, resulting in much less nested code:

if (!conditionA) {
  continue;
}
// ...
if (!conditionB) {
  continue;
}
// decode point


void unpack(const velodyne_msgs::msg::VelodynePacket & velodyne_packet)
{
const raw_packet_t * raw = (const raw_packet_t *)&velodyne_packet.data[0];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Never use C-style casts for converting byte arrays to structs. This is undefined behavior.
Use memcpy instead: please refer to the Hesai decoder for a safe implementation.

Please also move parsing to the existing parsePacket member function for readability.

@ike-kazu
Copy link
Contributor

ike-kazu commented May 8, 2024

Thank you @bgilby59 for refactoring velodyne drivers!
I tested this and report the result below. Please confirm it!

Pointcloud Output

Field Type
azimuth screenshot_from_2024-05-01_17-34-49_720
channel screenshot_from_2024-05-01_17-37-36_720
distance screenshot_from_2024-05-01_17-40-35_720
elevation screenshot_from_2024-05-01_17-40-54_720
intensity screenshot_from_2024-05-01_17-41-18_720
return_type screenshot_from_2024-05-01_17-41-53_720
timestamp screenshot_from_2024-05-01_17-42-03_720

🟢Pointcloud Output is looking same as before! It is great refactoring!!

Decoder Performance

timing_comparison_720
🟢Performance is looking great! It is faster than before with your work!!

@bgilby59
Copy link
Contributor Author

bgilby59 commented May 13, 2024

@mojomex @ike-kazu
Thank you for your review, testing, and comments! Update: I don't currently have access to a ros environment for testing, but I think I will be able to handle your comments later this month. Sorry for the delay!

@mojomex
Copy link
Collaborator

mojomex commented May 22, 2024

As @ike-kazu is currently continuing @bgilby59's work in #144, I will close this PR.

@mojomex mojomex closed this May 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants