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

Merged
Changes from 4 commits
Commits
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
122 changes: 111 additions & 11 deletions code/perception/src/radar_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import rospy
import ros_numpy
import numpy as np
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
Comment on lines +5 to +8
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.



class RadarNode:
Expand All @@ -12,19 +14,19 @@ class RadarNode:
"""

def callback(self, data):
"""Process radar Point2Cloud data and publish minimum velocity.
"""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
"""

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.

clustered_points_json = json.dumps(clustered_points)
self.dist_array_radar_publisher.publish(clustered_points_json)
Comment on lines +24 to +26
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}")


def listener(self):
"""
Expand All @@ -35,8 +37,11 @@ def listener(self):

# publisher for radar dist_array
self.dist_array_radar_publisher = rospy.Publisher(
rospy.get_param("~image_distance_topic", "/paf/hero/Radar/velocity"),
Float32,
rospy.get_param(
"~image_distance_topic",
"/paf/hero/Radar/dist_array_unsegmented",
),
String,
queue_size=10,
)

Expand All @@ -49,6 +54,101 @@ def listener(self):
rospy.spin()


def pointcloud2_to_array(pointcloud_msg):
"""
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.
"""
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
)
Comment on lines +60 to +63
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([])


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
):
Comment on lines +69 to +71
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")

"""
Filtert und gruppiert Punkte aus einer ROS-PointCloud2-Nachricht
basierend auf DBSCAN-Clustering.

Parameter:
- pointcloud_msg: sensor_msgs/PointCloud2
Die ROS-PointCloud2-Nachricht mit den 3D-Punkten.
- max_distance: float
Maximale Entfernung, um Punkte zu berücksichtigen. Punkte außerhalb dieser
Entfernung werden verworfen.
- eps: float, optional (default: 1.0)
Der maximale Abstand zwischen zwei Punkten, damit diese im selben Cluster
sein können.
- min_samples: int, optional (default: 2)
Die minimale Anzahl von Punkten, die erforderlich sind, um einen Cluster
zu bilden.

Rückgabewert:
- 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.

Funktionalität:
- 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.
"""
data = pointcloud2_to_array(pointcloud_msg)

# Filtere Punkte basierend auf der maximalen Entfernung
filtered_data = data[data[:, 3] < max_distance]

# Zusätzlicher Filter auf Y- und Z-Werte
filtered_data = filtered_data[
(filtered_data[:, 1] >= -1)
& (filtered_data[:, 1] <= 1)
& (filtered_data[:, 2] <= 1.3)
& (filtered_data[:, 2] >= -0.7)
]
Comment on lines +96 to +101
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)
]


# Rückgabe eines leeren Dictionaries, wenn keine Punkte übrig sind
if len(filtered_data) == 0:
return {}

# Extrahiere die 2D-Koordinaten (x, y) für das Clustering
coordinates = filtered_data[:, :2]
clustering = DBSCAN(eps=eps, min_samples=min_samples).fit(coordinates)
labels = clustering.labels_

# 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()
}
return clustered_points


if __name__ == "__main__":
radar_node = RadarNode()
radar_node.listener()
Loading