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

523 Distance measurement of not detected objects for radar data #525

Conversation

Ralf524
Copy link
Collaborator

@Ralf524 Ralf524 commented Nov 24, 2024

Description

Added clustering for radar data points

Fixes # (523)

Type of change

Please delete options that are not relevant.

  • New feature (non-breaking change which adds functionality)

Does this PR introduce a breaking change?

No

Most important changes

radar_node

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works (might be obsolete with CI later on)
  • New and existing unit tests pass locally with my changes (might be obsolete with CI later on)

Summary by CodeRabbit

  • New Features

    • Enhanced radar data processing with clustering capabilities.
    • Introduced a new topic for publishing radar data as a string message.
    • Added functionality to convert point cloud messages into structured arrays.
    • Implemented clustering of radar data based on specified parameters.
  • Bug Fixes

    • Updated message types to improve data handling and communication.

@Ralf524 Ralf524 linked an issue Nov 24, 2024 that may be closed by this pull request
Copy link
Contributor

coderabbitai bot commented Nov 24, 2024

Walkthrough

The changes in this pull request primarily focus on the RadarNode class within radar_node.py. The callback method has been significantly modified to process radar data by clustering data points and publishing the results as a String message, rather than extracting minimum velocity. A new function for clustering, cluster_radar_data_from_pointcloud, has been introduced, along with a helper function to convert point cloud messages into an array format. The publisher topic has also been updated to reflect these changes.

Changes

File Change Summary
code/perception/src/radar_node.py - Updated callback method to cluster radar data and publish as String.
- Added pointcloud2_to_array method for converting point cloud messages to structured arrays.
- Added cluster_radar_data_from_pointcloud method for clustering radar data.
- Changed publisher topic from "/paf/hero/Radar/velocity" to "/paf/hero/Radar/dist_array_unsegmented".

Possibly related PRs

  • 466 add radar sensor #522: The changes in this PR involve modifications to the RadarNode class in radar_node.py, which is directly related to the main PR's updates to the same class, specifically the callback method and the introduction of new methods for processing radar data.

Suggested reviewers

  • Lukasnol

Poem

🐰 In the radar's dance, we now cluster and weave,
Data points gather, a new tale to conceive.
From velocity's whisper to strings that unite,
A symphony of signals, in the soft moonlight.
Hopping through code, with joy we explore,
Each change a new journey, forever to soar! 🌟

Warning

Rate limit exceeded

@michalal7 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 40 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between b99d8f9 and b4e217c.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (4)
code/perception/src/radar_node.py (4)

27-27: Avoid hardcoding parameters; make 'max_distance' configurable

Passing a hardcoded value 10 for max_distance reduces flexibility. Consider retrieving max_distance from ROS parameters or making it a class attribute to allow dynamic configuration.

Apply this diff to retrieve max_distance from a ROS parameter:

 def callback(self, data):
+    max_distance = rospy.get_param("~max_distance", 10)
-    clustered_points = cluster_radar_data_from_pointcloud(data, 10)
+    clustered_points = cluster_radar_data_from_pointcloud(data, max_distance)

41-42: Update parameter name for consistency

The parameter name ~image_distance_topic may not accurately reflect the radar data being published. Consider renaming it to ~radar_distance_topic for clarity and consistency.

Apply this diff:

 self.dist_array_radar_publisher = rospy.Publisher(
-    rospy.get_param(
-        "~image_distance_topic", "/paf/hero/Radar/dist_array_unsegmented"
-    ),
+    rospy.get_param(
+        "~radar_distance_topic", "/paf/hero/Radar/dist_array_unsegmented"
+    ),
     String,
     queue_size=10,
 )

Ensure that any configurations or launch files are updated accordingly.


66-85: Include docstrings for new functions

Please add docstrings to the pointcloud2_to_array and cluster_radar_data_from_pointcloud functions to explain their purpose, parameters, and return values. This improves code readability and maintainability.

Apply this diff to add docstrings:

 def pointcloud2_to_array(pointcloud_msg):
+    """Converts a PointCloud2 message into a NumPy array with x, y, z coordinates and distances.
+
+    Args:
+        pointcloud_msg (PointCloud2): The PointCloud2 message to convert.
+
+    Returns:
+        numpy.ndarray: An array with columns [x, y, z, distance].
+    """
     cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
     distances = np.sqrt(
         cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
     )
     return np.column_stack(
         (cloud_array["x"], cloud_array["y"], cloud_array["z"], distances)
     )

 def cluster_radar_data_from_pointcloud(
     pointcloud_msg, max_distance, eps=1.0, min_samples=2
 ):
+    """Clusters radar data points from a PointCloud2 message using DBSCAN.
+
+    Args:
+        pointcloud_msg (PointCloud2): The PointCloud2 message containing radar data.
+        max_distance (float): Maximum distance to consider for data points.
+        eps (float, optional): The maximum distance between two samples for them to be considered as in the same neighborhood.
+        min_samples (int, optional): The number of samples in a neighborhood for a point to be considered as a core point.
+
+    Returns:
+        dict: A dictionary where keys are cluster labels and values are the count of points in each cluster.
+    """
     data = pointcloud2_to_array(pointcloud_msg)
     filtered_data = data[data[:, 3] < max_distance]
     # Rest of the function...

77-78: Log a warning when no data points are found after filtering

If filtered_data is empty, it might be helpful to log a warning to inform that no data points met the filtering criteria.

Apply this diff:

 if len(filtered_data) == 0:
+    rospy.logwarn("No data points found after filtering in cluster_radar_data_from_pointcloud.")
     return {}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between abcf83b and f764ca3.

📒 Files selected for processing (1)
  • code/perception/src/radar_node.py (4 hunks)

Comment on lines +5 to +8
from std_msgs.msg import String
from sensor_msgs.msg import PointCloud2
from std_msgs.msg import Float32
from sklearn.cluster import DBSCAN
import json
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ensure 'scikit-learn' is included in project dependencies

The DBSCAN class from sklearn.cluster is being used. Please ensure that scikit-learn is added to your project's dependencies in requirements.txt or setup.py so that it is installed during setup.

Comment on lines 17 to 24
"""Process radar Point2Cloud data and publish clustered data points.

Extracts velocity information from radar data
and publishes the minimum velocity as a
Float32 message
Extracts information from radar data
and publishes the clustered radar data
points as a String message

Args:
data: Point2Cloud message containing radar data with velocity field
data: Point2Cloud message containing radar data
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Fix typos and improve docstring formatting

  • Replace Point2Cloud with PointCloud2 to correct the typo.
  • Adjust the docstring to follow PEP 257 conventions by adding a blank line after the summary and consolidating the description.

Apply this diff to correct the docstring:

 def callback(self, data):
-    """Process radar Point2Cloud data and publish clustered data points.
-
-    Extracts information from radar data
-    and publishes the clustered radar data
-    points as a String message
+    """Process radar PointCloud2 data and publish clustered data points.
+
+    Extracts information from radar data and publishes the clustered radar data
+    points as a `String` message.
     Args:
-        data: Point2Cloud message containing radar data
+        data: PointCloud2 message containing radar data
     """
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"""Process radar Point2Cloud data and publish clustered data points.
Extracts velocity information from radar data
and publishes the minimum velocity as a
Float32 message
Extracts information from radar data
and publishes the clustered radar data
points as a String message
Args:
data: Point2Cloud message containing radar data with velocity field
data: Point2Cloud message containing radar data
"""Process radar PointCloud2 data and publish clustered data points.
Extracts information from radar data and publishes the clustered radar data
points as a `String` message.
Args:
data: PointCloud2 message containing radar data

Comment on lines 56 to 63
def pointcloud2_to_array(pointcloud_msg):
cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
distances = np.sqrt(
cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
)
return np.column_stack(
(cloud_array["x"], cloud_array["y"], cloud_array["z"], distances)
)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add exception handling for point cloud conversion

Converting the PointCloud2 message to an array may raise exceptions if the message is malformed. Add exception handling to prevent the node from crashing due to unexpected input.

Apply this diff to add exception handling:

 def pointcloud2_to_array(pointcloud_msg):
+    try:
         cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
+    except Exception as e:
+        rospy.logerr(f"Failed to convert PointCloud2 to array: {e}")
+        return np.array([])
     distances = np.sqrt(
         cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
     )
     return np.column_stack(
         (cloud_array["x"], cloud_array["y"], cloud_array["z"], distances)
     )

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Collaborator

@Lukasnol Lukasnol left a comment

Choose a reason for hiding this comment

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

Maybe you could add some explanations as comments in your Code. For example "cluster_radar_data_from_pointcloud". The method name is self explaining, but maybe you can give some insight on inputs and outputs.

New functions have been annotated with comments.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (2)
code/perception/src/radar_node.py (2)

58-69: Translate German documentation to English

For consistency and maintainability, documentation should be in English.

Apply this diff:

-    Konvertiert eine ROS-PointCloud2-Nachricht in ein NumPy-Array und berechnet die euklidischen Entfernungen
-    jedes Punkts vom Ursprung.
+    Converts a ROS PointCloud2 message to a NumPy array and calculates the Euclidean distances
+    of each point from the origin.

-    Parameter:
+    Args:
     - pointcloud_msg: sensor_msgs/PointCloud2
-        Die ROS-PointCloud2-Nachricht, die die 3D-Punkte enthält.
+        The ROS PointCloud2 message containing 3D points.

-    Rückgabewert:
+    Returns:
     - np.ndarray
-        Ein 2D-Array, in dem jede Zeile einem Punkt in der Punktwolke entspricht:
-        [x, y, z, distance], wobei "distance" die Entfernung vom Ursprung ist.
+        A 2D array where each row corresponds to a point in the point cloud:
+        [x, y, z, distance], where "distance" is the distance from origin.

84-106: Translate German documentation to English

For consistency and maintainability, documentation should be in English.

Apply this diff:

-    Filtert und gruppiert Punkte aus einer ROS-PointCloud2-Nachricht basierend auf DBSCAN-Clustering.
+    Filters and groups points from a ROS PointCloud2 message based on DBSCAN clustering.

-    Parameter:
+    Args:
     - pointcloud_msg: sensor_msgs/PointCloud2
-        Die ROS-PointCloud2-Nachricht mit den 3D-Punkten.
+        The ROS PointCloud2 message containing 3D points.
     - max_distance: float
-        Maximale Entfernung, um Punkte zu berücksichtigen. Punkte außerhalb dieser Entfernung werden verworfen.
+        Maximum distance to consider points. Points beyond this distance are discarded.
     - eps: float, optional (default: 1.0)
-        Der maximale Abstand zwischen zwei Punkten, damit diese im selben Cluster sein können.
+        The maximum distance between two points to be considered in the same cluster.
     - min_samples: int, optional (default: 2)
-        Die minimale Anzahl von Punkten, die erforderlich sind, um einen Cluster zu bilden.
+        The minimum number of points required to form a cluster.

-    Rückgabewert:
+    Returns:
     - dict
-        Ein Dictionary, in dem die Schlüssel die Cluster-Labels (int) sind und die Werte die Anzahl der Punkte
-        in jedem Cluster. Wenn keine Punkte vorhanden sind, wird ein leeres Dictionary zurückgegeben.
+        A dictionary where keys are cluster labels (int) and values are the number of points
+        in each cluster. Returns an empty dictionary if no points are present.

-    Funktionalität:
+    Functionality:
-    - Die Funktion konvertiert die Punktwolke in ein Array mit Punktkoordinaten und berechneten Entfernungen.
-    - Punkte außerhalb eines spezifizierten Entfernungsbereichs und räumlichen Filtergrenzen werden verworfen.
-    - Anschließend wird DBSCAN-Clustering auf die 2D-Koordinaten (x, y) der Punkte angewendet.
-    - Die Cluster-Labels und ihre Häufigkeiten werden als Dictionary zurückgegeben.
+    - Converts the point cloud to an array with point coordinates and calculated distances.
+    - Discards points outside specified distance range and spatial filter boundaries.
+    - Applies DBSCAN clustering on the 2D coordinates (x, y) of the points.
+    - Returns cluster labels and their frequencies as a dictionary.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f764ca3 and 0540464.

📒 Files selected for processing (1)
  • code/perception/src/radar_node.py (4 hunks)
🔇 Additional comments (1)
code/perception/src/radar_node.py (1)

126-127: Verify DBSCAN clustering parameters

The default parameters (eps=1.0, min_samples=2) might need adjustment based on your radar's characteristics and the expected object sizes/distances.

Run this script to analyze the clustering results with different parameters:

✅ Verification successful

Let me gather more context about the parameter values and their configuration.


Let me check the function's usage and any test cases that might validate these parameters.


Let me check the radar node implementation to understand how these parameters are being used in the broader context.


DBSCAN parameters are appropriately configured for radar clustering

The current DBSCAN parameters (eps=1.0, min_samples=2) are well-suited for radar point clustering because:

  • The implementation includes proper spatial filtering (y: [-1,1], z: [-0.7,1.3]) before clustering
  • The clustering is performed on 2D coordinates (x,y) which is appropriate for radar-based object detection
  • The eps=1.0 (meter) is a reasonable distance threshold for grouping radar points from the same object
  • The min_samples=2 is suitable for radar data as it allows detection of small objects while filtering noise
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Analyze clustering results with different parameters
# Expected: Find optimal eps and min_samples values

# Search for any existing parameter tuning tests or documentation
rg -g '!*.pyc' -g '!*.git*' -A 5 'DBSCAN.*eps.*min_samples'

# Search for radar-specific configuration files
fd -e yaml -e launch radar

Length of output: 536


Script:

#!/bin/bash
# Search for parameter definitions and configurations
rg -g '!*.pyc' -g '!*.git*' -A 5 'eps.*=|min_samples.*=' code/perception/

Length of output: 914


Script:

#!/bin/bash
# Search for function calls and test cases
rg -g '!*.pyc' -g '!*.git*' "filter_and_group_points" code/perception/

Length of output: 70


Script:

#!/bin/bash
# Get the full radar node implementation
cat code/perception/src/radar_node.py

Length of output: 4862

Comment on lines +80 to +82
def cluster_radar_data_from_pointcloud(
pointcloud_msg, max_distance, eps=1.0, min_samples=2
):
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input parameter validation

The function should validate input parameters to prevent invalid configurations.

Apply this diff:

 def cluster_radar_data_from_pointcloud(
     pointcloud_msg, max_distance, eps=1.0, min_samples=2
 ):
+    if max_distance <= 0:
+        raise ValueError("max_distance must be positive")
+    if eps <= 0:
+        raise ValueError("eps must be positive")
+    if min_samples < 2:
+        raise ValueError("min_samples must be at least 2")
+    if not isinstance(pointcloud_msg, PointCloud2):
+        raise TypeError("pointcloud_msg must be a PointCloud2 message")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def cluster_radar_data_from_pointcloud(
pointcloud_msg, max_distance, eps=1.0, min_samples=2
):
def cluster_radar_data_from_pointcloud(
pointcloud_msg, max_distance, eps=1.0, min_samples=2
):
if max_distance <= 0:
raise ValueError("max_distance must be positive")
if eps <= 0:
raise ValueError("eps must be positive")
if min_samples < 2:
raise ValueError("min_samples must be at least 2")
if not isinstance(pointcloud_msg, PointCloud2):
raise TypeError("pointcloud_msg must be a PointCloud2 message")

Comment on lines +27 to +29
clustered_points = cluster_radar_data_from_pointcloud(data, 10)
clustered_points_json = json.dumps(clustered_points)
self.dist_array_radar_publisher.publish(clustered_points_json)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for JSON serialization

The callback should handle potential exceptions during clustering and JSON serialization to prevent node crashes.

Apply this diff:

-        clustered_points = cluster_radar_data_from_pointcloud(data, 10)
-        clustered_points_json = json.dumps(clustered_points)
-        self.dist_array_radar_publisher.publish(clustered_points_json)
+        try:
+            clustered_points = cluster_radar_data_from_pointcloud(data, 10)
+            clustered_points_json = json.dumps(clustered_points)
+            self.dist_array_radar_publisher.publish(clustered_points_json)
+        except Exception as e:
+            rospy.logerr(f"Failed to process radar data: {e}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
clustered_points = cluster_radar_data_from_pointcloud(data, 10)
clustered_points_json = json.dumps(clustered_points)
self.dist_array_radar_publisher.publish(clustered_points_json)
try:
clustered_points = cluster_radar_data_from_pointcloud(data, 10)
clustered_points_json = json.dumps(clustered_points)
self.dist_array_radar_publisher.publish(clustered_points_json)
except Exception as e:
rospy.logerr(f"Failed to process radar data: {e}")

Comment on lines +113 to +118
filtered_data = filtered_data[
(filtered_data[:, 1] >= -1)
& (filtered_data[:, 1] <= 1)
& (filtered_data[:, 2] <= 1.3)
& (filtered_data[:, 2] >= -0.7)
]
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make filter boundaries configurable via ROS parameters

Hard-coded filter values should be configurable to allow for easy adjustment without code changes.

Apply this diff:

+        # Get filter boundaries from ROS parameters with defaults
+        y_min = rospy.get_param("~filter/y_min", -1.0)
+        y_max = rospy.get_param("~filter/y_max", 1.0)
+        z_min = rospy.get_param("~filter/z_min", -0.7)
+        z_max = rospy.get_param("~filter/z_max", 1.3)
+
         filtered_data = filtered_data[
-            (filtered_data[:, 1] >= -1)
-            & (filtered_data[:, 1] <= 1)
-            & (filtered_data[:, 2] <= 1.3)
-            & (filtered_data[:, 2] >= -0.7)
+            (filtered_data[:, 1] >= y_min)
+            & (filtered_data[:, 1] <= y_max)
+            & (filtered_data[:, 2] <= z_max)
+            & (filtered_data[:, 2] >= z_min)
         ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
filtered_data = filtered_data[
(filtered_data[:, 1] >= -1)
& (filtered_data[:, 1] <= 1)
& (filtered_data[:, 2] <= 1.3)
& (filtered_data[:, 2] >= -0.7)
]
# Get filter boundaries from ROS parameters with defaults
y_min = rospy.get_param("~filter/y_min", -1.0)
y_max = rospy.get_param("~filter/y_max", 1.0)
z_min = rospy.get_param("~filter/z_min", -0.7)
z_max = rospy.get_param("~filter/z_max", 1.3)
filtered_data = filtered_data[
(filtered_data[:, 1] >= y_min)
& (filtered_data[:, 1] <= y_max)
& (filtered_data[:, 2] <= z_max)
& (filtered_data[:, 2] >= z_min)
]

Whitespace elimination.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
code/perception/src/radar_node.py (2)

27-27: Make max_distance configurable via ROS parameter

The max_distance value is hardcoded to 10. Consider making it configurable via ROS parameters for better flexibility.

-        clustered_points = cluster_radar_data_from_pointcloud(data, 10)
+        max_distance = rospy.get_param('~clustering/max_distance', 10.0)
+        clustered_points = cluster_radar_data_from_pointcloud(data, max_distance)

124-126: Consider enhancing clustering capabilities

The current implementation performs clustering only on 2D coordinates (x, y). Consider these potential improvements:

  1. Use 3D coordinates for clustering to capture vertical patterns
  2. Incorporate velocity data (if available in radar points) for better object separation

Example enhancement:

-    # Extrahiere die 2D-Koordinaten (x, y) für das Clustering
-    coordinates = filtered_data[:, :2]
+    # Use 3D coordinates for clustering
+    coordinates = filtered_data[:, :3]
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0540464 and 30a569c.

📒 Files selected for processing (1)
  • code/perception/src/radar_node.py (4 hunks)
🔇 Additional comments (1)
code/perception/src/radar_node.py (1)

40-43: LGTM: Publisher configuration is well-structured

The publisher configuration follows ROS conventions and uses appropriate parameters for JSON message publishing.

Comment on lines 58 to 69
Konvertiert eine ROS-PointCloud2-Nachricht in ein NumPy-Array und berechnet die euklidischen Entfernungen
jedes Punkts vom Ursprung.

Parameter:
- pointcloud_msg: sensor_msgs/PointCloud2
Die ROS-PointCloud2-Nachricht, die die 3D-Punkte enthält.

Rückgabewert:
- np.ndarray
Ein 2D-Array, in dem jede Zeile einem Punkt in der Punktwolke entspricht:
[x, y, z, distance], wobei "distance" die Entfernung vom Ursprung ist.
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Translate documentation to English for consistency

The docstrings are written in German while the rest of the codebase appears to be in English. Consider translating them for better maintainability and consistency.

Example translation for pointcloud2_to_array:

-    Konvertiert eine ROS-PointCloud2-Nachricht in ein NumPy-Array und berechnet die euklidischen Entfernungen
-    jedes Punkts vom Ursprung.
+    Converts a ROS PointCloud2 message to a NumPy array and calculates the Euclidean distances
+    of each point from the origin.

-    Parameter:
+    Parameters:
     - pointcloud_msg: sensor_msgs/PointCloud2
-        Die ROS-PointCloud2-Nachricht, die die 3D-Punkte enthält.
+        The ROS PointCloud2 message containing 3D points.

-    Rückgabewert:
+    Returns:
     - np.ndarray
-        Ein 2D-Array, in dem jede Zeile einem Punkt in der Punktwolke entspricht:
-        [x, y, z, distance], wobei "distance" die Entfernung vom Ursprung ist.
+        A 2D array where each row corresponds to a point in the point cloud:
+        [x, y, z, distance], where "distance" is the distance from origin.

Also applies to: 84-106

maximal line length of 88 characters.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (4)
code/perception/src/radar_node.py (4)

27-27: Make clustering parameters configurable via ROS parameters

The max_distance parameter is hardcoded to 10. Consider making this configurable via ROS parameters for flexibility.

-        clustered_points = cluster_radar_data_from_pointcloud(data, 10)
+        max_distance = rospy.get_param("~clustering/max_distance", 10.0)
+        eps = rospy.get_param("~clustering/eps", 1.0)
+        min_samples = rospy.get_param("~clustering/min_samples", 2)
+        clustered_points = cluster_radar_data_from_pointcloud(
+            data, max_distance, eps, min_samples
+        )

139-148: Simplify cluster counting logic

The current implementation of counting cluster points can be simplified using numpy:

-    # Zählen der Punktanzahl in jedem Cluster
-    clustered_points = {
-        label: list(labels).count(label)
-        for label in set(labels)
-    }
-
-    # Konvertiere die Labels in Integer und gebe das Dictionary zurück
-    clustered_points = {
-        int(label): count
-        for label, count in clustered_points.items()
-    }
+    # Count points in each cluster using numpy
+    unique_labels, counts = np.unique(labels, return_counts=True)
+    clustered_points = dict(zip(unique_labels.astype(int), counts))

This approach is more efficient as it:

  1. Uses numpy's optimized counting functions
  2. Avoids multiple dictionary comprehensions
  3. Performs the integer conversion in a single step

134-136: Consider adding cluster quality metrics

The clustering results could benefit from additional quality metrics to help evaluate the effectiveness of the clustering:

     coordinates = filtered_data[:, :2]
     clustering = DBSCAN(eps=eps, min_samples=min_samples).fit(coordinates)
     labels = clustering.labels_
+    
+    # Calculate clustering quality metrics
+    n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
+    n_noise = list(labels).count(-1)
+    rospy.logdebug(f"Found {n_clusters} clusters and {n_noise} noise points")

81-83: Add unit tests for clustering functionality

Given the complexity of the clustering logic and its importance for object detection, comprehensive unit tests should be added to verify:

  1. Clustering behavior with various point distributions
  2. Edge cases (empty point clouds, single points, etc.)
  3. Filter boundary conditions
  4. Output format consistency

Would you like me to help generate unit tests for these scenarios?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 30a569c and b99d8f9.

📒 Files selected for processing (1)
  • code/perception/src/radar_node.py (4 hunks)

Comment on lines +71 to +74
cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
distances = np.sqrt(
cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for numpy operations

The numpy operations could raise exceptions with invalid input data. Consider adding error handling:

-    cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
-    distances = np.sqrt(
-        cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
-    )
+    try:
+        cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
+        distances = np.sqrt(
+            cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
+        )
+    except (ValueError, KeyError) as e:
+        rospy.logerr(f"Error processing point cloud data: {e}")
+        return np.array([])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
distances = np.sqrt(
cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
)
try:
cloud_array = ros_numpy.point_cloud2.pointcloud2_to_array(pointcloud_msg)
distances = np.sqrt(
cloud_array["x"] ** 2 + cloud_array["y"] ** 2 + cloud_array["z"] ** 2
)
except (ValueError, KeyError) as e:
rospy.logerr(f"Error processing point cloud data: {e}")
return np.array([])

Translated comments to englisch and reformated code.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (3)
code/perception/src/radar_node.py (3)

64-66: Consider optimizing memory usage

The column_stack operation creates a new array in memory. For large point clouds, consider using pre-allocated arrays or in-place operations.

-    return np.column_stack(
-        (cloud_array["x"], cloud_array["y"], cloud_array["z"], distances)
-    )
+    result = np.empty((len(cloud_array), 4), dtype=np.float32)
+    result[:, 0] = cloud_array["x"]
+    result[:, 1] = cloud_array["y"]
+    result[:, 2] = cloud_array["z"]
+    result[:, 3] = distances
+    return result

104-108: Enhance clustering output with additional metrics

Consider including more useful metrics for each cluster, such as centroid and bounding box, to provide more context for downstream processing.

-    clustered_points = {label: list(labels).count(label) for label in set(labels)}
-    clustered_points = {int(label): count for label, count in clustered_points.items()}
+    clustered_points = {}
+    for label in set(labels):
+        if label == -1:  # Skip noise points
+            continue
+        mask = labels == label
+        points = coordinates[mask]
+        clustered_points[int(label)] = {
+            'count': np.sum(mask),
+            'centroid': points.mean(axis=0).tolist(),
+            'bbox': {
+                'min': points.min(axis=0).tolist(),
+                'max': points.max(axis=0).tolist()
+            }
+        }

104-105: Consider optimizing DBSCAN for real-time performance

For real-time radar processing, consider using more efficient clustering methods or optimizing DBSCAN parameters:

  1. Use algorithm='ball_tree' for better performance with 2D data
  2. Set n_jobs=-1 to utilize multiple CPU cores
-    clustering = DBSCAN(eps=eps, min_samples=min_samples).fit(coordinates)
+    clustering = DBSCAN(
+        eps=eps,
+        min_samples=min_samples,
+        algorithm='ball_tree',
+        n_jobs=-1
+    ).fit(coordinates)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b99d8f9 and b4e217c.

📒 Files selected for processing (1)
  • code/perception/src/radar_node.py (1 hunks)
🔇 Additional comments (1)
code/perception/src/radar_node.py (1)

31-37: LGTM! Publisher configuration looks good.

The publisher configuration follows ROS conventions and properly uses parameter overrides for topic names.

coordinates = ros_numpy.point_cloud2.pointcloud2_to_array(data)
msg = np.min(coordinates["Velocity"])
self.dist_array_radar_publisher.publish(msg)
clustered_points = cluster_radar_data_from_pointcloud(data, 10)
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make clustering parameters configurable via ROS parameters

The max_distance parameter is hardcoded to 10. Consider making this configurable via ROS parameters for better flexibility.

-        clustered_points = cluster_radar_data_from_pointcloud(data, 10)
+        max_distance = rospy.get_param('~clustering/max_distance', 10.0)
+        eps = rospy.get_param('~clustering/eps', 1.0)
+        min_samples = rospy.get_param('~clustering/min_samples', 2)
+        clustered_points = cluster_radar_data_from_pointcloud(
+            data, max_distance, eps, min_samples
+        )

Committable suggestion skipped: line range outside the PR's diff.

@Lukasnol Lukasnol merged commit 03ec527 into main Nov 25, 2024
4 checks passed
@asamluka asamluka deleted the 523-general-distance-measurement-of-not-detected-objects-for-radar-data branch January 29, 2025 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

[General]: Distance measurement of not detected objects for radar data
3 participants