Skip to content

Commit e88bbaf

Browse files
committed
Add input validation and error handling in depth processing methods
1 parent 193c545 commit e88bbaf

File tree

1 file changed

+128
-13
lines changed

1 file changed

+128
-13
lines changed

yolo_ros/yolo_ros/detect_3d_node.py

Lines changed: 128 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,27 @@ def compute_depth_bounds(depth_values: np.ndarray) -> Tuple[float, float, float]
299299
Returns:
300300
Tuple of (z_center, z_min, z_max) representing the object's depth
301301
"""
302+
# Basic input validation
303+
if not isinstance(depth_values, np.ndarray):
304+
return 0.0, 0.0, 0.0
305+
306+
if len(depth_values) == 0:
307+
return 0.0, 0.0, 0.0
308+
309+
# Ensure all values are numeric and finite
310+
try:
311+
depth_values = np.asarray(depth_values, dtype=np.float64)
312+
valid_mask = np.isfinite(depth_values) & (depth_values > 0)
313+
depth_values = depth_values[valid_mask]
314+
except (ValueError, TypeError):
315+
return 0.0, 0.0, 0.0
316+
317+
if len(depth_values) == 0:
318+
return 0.0, 0.0, 0.0
319+
302320
if len(depth_values) < 4:
303-
z_center = np.median(depth_values)
304-
return z_center, np.min(depth_values), np.max(depth_values)
321+
z_center = float(np.median(depth_values))
322+
return z_center, float(np.min(depth_values)), float(np.max(depth_values))
305323

306324
sorted_depths = np.sort(depth_values)
307325
n = len(sorted_depths)
@@ -372,7 +390,7 @@ def compute_depth_bounds(depth_values: np.ndarray) -> Tuple[float, float, float]
372390
z_min = z_center - half_min
373391
z_max = z_center + half_min
374392

375-
return z_center, z_min, z_max
393+
return float(z_center), float(z_min), float(z_max)
376394

377395
@staticmethod
378396
def _density_based_cluster(
@@ -505,6 +523,12 @@ def convert_bb_to_3d(
505523
@param detection 2D detection to convert
506524
@return 3D bounding box or None if conversion fails
507525
"""
526+
# Basic input validations
527+
if depth_image is None or not isinstance(depth_image, np.ndarray):
528+
return None
529+
530+
if depth_image.size == 0:
531+
return None
508532

509533
center_x = int(detection.bbox.center.position.x)
510534
center_y = int(detection.bbox.center.position.y)
@@ -541,11 +565,23 @@ def convert_bb_to_3d(
541565
pixel_coords = np.column_stack([x_grid.flatten(), y_grid.flatten()])
542566

543567
roi = roi / self.depth_image_units_divisor # Convert to meters
568+
569+
# Validate that division did not produce NaN or inf
570+
if not np.any(np.isfinite(roi)):
571+
return None
572+
544573
if not np.any(roi):
545574
return None
546575

547576
# Extract valid depth values with their spatial positions
548577
valid_depths = roi.flatten()
578+
579+
# Ensure correct numeric type
580+
try:
581+
valid_depths = np.asarray(valid_depths, dtype=np.float64)
582+
except (ValueError, TypeError):
583+
return None
584+
549585
valid_mask = (valid_depths > 0) & np.isfinite(valid_depths)
550586
valid_depths = valid_depths[valid_mask]
551587
valid_coords = pixel_coords[valid_mask]
@@ -564,18 +600,26 @@ def convert_bb_to_3d(
564600
valid_depths, spatial_weights
565601
)
566602

567-
if z == 0:
603+
if not np.isfinite(z) or z == 0:
568604
return None
569605

570606
# Compute height (y-axis) statistics from actual 3D points
571607
y_center, y_min, y_max = Detect3DNode._compute_height_bounds(
572608
valid_coords, valid_depths, spatial_weights, depth_info
573609
)
610+
611+
# Validate results
612+
if not all(np.isfinite([y_center, y_min, y_max])):
613+
return None
574614

575615
# Compute width (x-axis) statistics from actual 3D points
576616
x_center, x_min, x_max = Detect3DNode._compute_width_bounds(
577617
valid_coords, valid_depths, spatial_weights, depth_info
578618
)
619+
620+
# Validate results
621+
if not all(np.isfinite([x_center, x_min, x_max])):
622+
return None
579623

580624
# All dimensions come from actual 3D point analysis
581625
x = x_center
@@ -646,6 +690,16 @@ def _compute_height_bounds(
646690
Returns:
647691
Tuple of (y_center, y_min, y_max) in meters
648692
"""
693+
# Input validations
694+
try:
695+
valid_depths = np.asarray(valid_depths, dtype=np.float64)
696+
spatial_weights = np.asarray(spatial_weights, dtype=np.float64)
697+
except (ValueError, TypeError):
698+
return 0.0, 0.0, 0.0
699+
700+
if len(valid_coords) == 0 or len(valid_depths) == 0:
701+
return 0.0, 0.0, 0.0
702+
649703
if len(valid_coords) < 4:
650704
# Fallback: just use simple projection
651705
k = depth_info.k
@@ -657,8 +711,17 @@ def _compute_height_bounds(
657711
# Convert pixel coordinates to 3D y-coordinates
658712
k = depth_info.k
659713
py, fy = k[5], k[4]
714+
715+
# Validate camera parameters
716+
if fy == 0:
717+
return 0.0, 0.0, 0.0
718+
660719
y_coords_pixel = valid_coords[:, 1]
661720
y_3d = valid_depths * (y_coords_pixel - py) / fy
721+
722+
# Validate result
723+
if not np.any(np.isfinite(y_3d)):
724+
return 0.0, 0.0, 0.0
662725

663726
# Filter outliers using robust statistics
664727
# Compute weighted median as reference
@@ -728,7 +791,7 @@ def _compute_height_bounds(
728791
y_min = y_center - half_min
729792
y_max = y_center + half_min
730793

731-
return y_center, y_min, y_max
794+
return float(y_center), float(y_min), float(y_max)
732795

733796
@staticmethod
734797
def _compute_width_bounds(
@@ -750,6 +813,16 @@ def _compute_width_bounds(
750813
Returns:
751814
Tuple of (x_center, x_min, x_max) in meters
752815
"""
816+
# Input validations
817+
try:
818+
valid_depths = np.asarray(valid_depths, dtype=np.float64)
819+
spatial_weights = np.asarray(spatial_weights, dtype=np.float64)
820+
except (ValueError, TypeError):
821+
return 0.0, 0.0, 0.0
822+
823+
if len(valid_coords) == 0 or len(valid_depths) == 0:
824+
return 0.0, 0.0, 0.0
825+
753826
if len(valid_coords) < 4:
754827
# Fallback: just use simple projection
755828
k = depth_info.k
@@ -761,8 +834,17 @@ def _compute_width_bounds(
761834
# Convert pixel coordinates to 3D x-coordinates
762835
k = depth_info.k
763836
px, fx = k[2], k[0]
837+
838+
# Validate camera parameters
839+
if fx == 0:
840+
return 0.0, 0.0, 0.0
841+
764842
x_coords_pixel = valid_coords[:, 0]
765843
x_3d = valid_depths * (x_coords_pixel - px) / fx
844+
845+
# Validate result
846+
if not np.any(np.isfinite(x_3d)):
847+
return 0.0, 0.0, 0.0
766848

767849
# Filter outliers using robust statistics
768850
# Compute weighted median as reference
@@ -839,7 +921,7 @@ def _compute_width_bounds(
839921
x_min = x_center - half_min
840922
x_max = x_center + half_min
841923

842-
return x_center, x_min, x_max
924+
return float(x_center), float(x_min), float(x_max)
843925

844926
@staticmethod
845927
def _compute_depth_bounds_weighted(
@@ -855,9 +937,27 @@ def _compute_depth_bounds_weighted(
855937
Returns:
856938
Tuple of (z_center, z_min, z_max) representing the object's depth
857939
"""
940+
# Input validations
941+
try:
942+
depth_values = np.asarray(depth_values, dtype=np.float64)
943+
spatial_weights = np.asarray(spatial_weights, dtype=np.float64)
944+
except (ValueError, TypeError):
945+
return 0.0, 0.0, 0.0
946+
947+
if len(depth_values) == 0:
948+
return 0.0, 0.0, 0.0
949+
950+
# Validate that all values are finite
951+
valid_mask = np.isfinite(depth_values) & np.isfinite(spatial_weights)
952+
depth_values = depth_values[valid_mask]
953+
spatial_weights = spatial_weights[valid_mask]
954+
955+
if len(depth_values) == 0:
956+
return 0.0, 0.0, 0.0
957+
858958
if len(depth_values) < 4:
859-
z_center = np.median(depth_values)
860-
return z_center, np.min(depth_values), np.max(depth_values)
959+
z_center = float(np.median(depth_values))
960+
return z_center, float(np.min(depth_values)), float(np.max(depth_values))
861961

862962
# Step 1: Multi-scale histogram analysis for robust mode detection
863963
depth_range = np.ptp(depth_values)
@@ -988,7 +1088,7 @@ def _compute_depth_bounds_weighted(
9881088
z_min = z_center - half_min
9891089
z_max = z_center + half_min
9901090

991-
return z_center, z_min, z_max
1091+
return float(z_center), float(z_min), float(z_max)
9921092

9931093
def convert_keypoints_to_3d(
9941094
self,
@@ -1006,6 +1106,9 @@ def convert_keypoints_to_3d(
10061106
@param detection Detection containing 2D keypoints
10071107
@return Array of 3D keypoints
10081108
"""
1109+
# Validate input
1110+
if depth_image is None or not isinstance(depth_image, np.ndarray):
1111+
return KeyPoint3DArray()
10091112

10101113
# Build an array of 2D keypoints
10111114
keypoints_2d = np.array(
@@ -1016,8 +1119,20 @@ def convert_keypoints_to_3d(
10161119

10171120
# Sample depth image and project to 3D
10181121
z = depth_image[u, v]
1122+
1123+
# Validate and convert to float
1124+
try:
1125+
z = np.asarray(z, dtype=np.float64)
1126+
except (ValueError, TypeError):
1127+
return KeyPoint3DArray()
1128+
10191129
k = depth_info.k
10201130
px, py, fx, fy = k[2], k[5], k[0], k[4]
1131+
1132+
# Validate camera parameters
1133+
if fx == 0 or fy == 0:
1134+
return KeyPoint3DArray()
1135+
10211136
x = z * (v - px) / fx
10221137
y = z * (u - py) / fy
10231138
points_3d = (
@@ -1027,11 +1142,11 @@ def convert_keypoints_to_3d(
10271142
# Generate message
10281143
msg_array = KeyPoint3DArray()
10291144
for p, d in zip(points_3d, detection.keypoints.data):
1030-
if not np.isnan(p).any():
1145+
if not np.isnan(p).any() and np.all(np.isfinite(p)):
10311146
msg = KeyPoint3D()
1032-
msg.point.x = p[0]
1033-
msg.point.y = p[1]
1034-
msg.point.z = p[2]
1147+
msg.point.x = float(p[0])
1148+
msg.point.y = float(p[1])
1149+
msg.point.z = float(p[2])
10351150
msg.id = d.id
10361151
msg.score = d.score
10371152
msg_array.data.append(msg)

0 commit comments

Comments
 (0)