Skip to content

Commit 3d54689

Browse files
added obstacle detection pt2
1 parent 1c38690 commit 3d54689

3 files changed

Lines changed: 124 additions & 63 deletions

File tree

GEMstack/onboard/interface/gem_gazebo.py

Lines changed: 98 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from GEMstack.state.obstacle import ObstacleMaterialEnum, ObstacleState, ObstacleStateEnum
12
from .gem import *
23
from ...utils import settings
34
import math
@@ -53,7 +54,13 @@ class GNSSReading:
5354
'car': 'car',
5455
'vehicle': 'car',
5556
'truck': 'medium_truck',
56-
'large_truck': 'large_truck'
57+
'large_truck': 'large_truck',
58+
'cone': 'traffic_cone'
59+
}
60+
61+
# Map model prefixes to obstacle types
62+
MODEL_PREFIX_TO_OBSTACLE_TYPE = {
63+
'cone': 'traffic_cone'
5764
}
5865

5966
class GEMGazeboInterface(GEMInterface):
@@ -93,11 +100,15 @@ def __init__(self):
93100
self.model_states_sub = None
94101
self.tracked_model_prefixes = settings.get('simulator.agent_tracker.model_prefixes',
95102
['pedestrian', 'bicycle', 'car'])
103+
self.tracked_obstacle_prefixes = settings.get('simulator.obstacle_tracker.model_prefixes',
104+
['cone'])
96105
self.agent_detector_callback = None
106+
self.obstacle_detector_callback = None
97107
self.last_agent_positions = {}
98108
self.last_agent_velocities = {}
99109
self.last_model_states_time = 0.0
100110
self.agent_detection_rate = settings.get('simulator.agent_tracker.rate', 10.0) # Hz
111+
self.obstacle_detection_rate = settings.get('simulator.obstacle_tracker.rate', 50.0) # Hz
101112

102113
# Frame transformation variables
103114
self.start_pose_abs = None # Initial vehicle pose in GLOBAL frame
@@ -141,15 +152,15 @@ def model_states_callback(self, msg: ModelStates):
141152
current_time = self.time()
142153

143154
# Check if we should process this update (rate limiting)
144-
if current_time - self.last_model_states_time < 1.0/self.agent_detection_rate:
155+
if ((current_time - self.last_model_states_time < 1.0/self.agent_detection_rate) and (current_time - self.last_model_states_time < 1.0/self.obstacle_detection_rate)):
145156
return
146157

147158
# Calculate time delta since last update
148159
dt = current_time - self.last_model_states_time
149160
self.last_model_states_time = current_time
150161

151162
# Skip if no callback is registered
152-
if self.agent_detector_callback is None:
163+
if self.agent_detector_callback is None and self.obstacle_detector_callback is None:
153164
return
154165

155166
# Find vehicle in model states
@@ -203,16 +214,24 @@ def model_states_callback(self, msg: ModelStates):
203214
agent_type = None
204215
for prefix in self.tracked_model_prefixes:
205216
if model_name.lower().startswith(prefix.lower()):
206-
# Find the appropriate agent type from the prefix
207217
for key, value in MODEL_PREFIX_TO_AGENT_TYPE.items():
208218
if prefix.lower().startswith(key.lower()):
209219
agent_type = value
210220
break
211221
break
222+
223+
obstacle_type = None
224+
for prefix in self.tracked_obstacle_prefixes:
225+
if model_name.lower().startswith(prefix.lower()):
226+
for key, value in MODEL_PREFIX_TO_OBSTACLE_TYPE.items():
227+
if prefix.lower().startswith(key.lower()):
228+
obstacle_type = value
229+
break
230+
break
212231

213-
if agent_type is None:
232+
if agent_type is None and obstacle_type is None:
214233
continue # Not an agent we're tracking
215-
234+
216235
# Get position and orientation from model states
217236
position = msg.pose[i].position
218237
orientation = msg.pose[i].orientation
@@ -224,7 +243,6 @@ def model_states_callback(self, msg: ModelStates):
224243
# Convert orientation quaternion to euler angles
225244
quaternion = (orientation.x, orientation.y, orientation.z, orientation.w)
226245
roll, pitch, yaw = euler_from_quaternion(quaternion)
227-
228246
# Create agent pose in ABSOLUTE_CARTESIAN frame (Gazebo's native frame)
229247
agent_global_pose = ObjectPose(
230248
frame=ObjectFrameEnum.ABSOLUTE_CARTESIAN,
@@ -269,61 +287,72 @@ def model_states_callback(self, msg: ModelStates):
269287
# If transformation not initialized yet, just use the model_states pose
270288
agent_pose = agent_global_pose
271289

272-
# Calculate velocity manually if twist data is zero or missing
273-
velocity = (linear_vel.x, linear_vel.y, linear_vel.z)
274-
velocity_is_zero = abs(linear_vel.x) < 1e-6 and abs(linear_vel.y) < 1e-6 and abs(linear_vel.z) < 1e-6
275-
276-
if velocity_is_zero and model_name in self.last_agent_positions and dt > 0:
277-
# Calculate velocity from position difference
278-
prev_pos = self.last_agent_positions[model_name]
279-
dx = position.x - prev_pos[0]
280-
dy = position.y - prev_pos[1]
281-
dz = position.z - prev_pos[2]
290+
if agent_type is not None:
291+
# Calculate velocity manually if twist data is zero or missing
292+
velocity = (linear_vel.x, linear_vel.y, linear_vel.z)
293+
velocity_is_zero = abs(linear_vel.x) < 1e-6 and abs(linear_vel.y) < 1e-6 and abs(linear_vel.z) < 1e-6
282294

283-
# Calculate velocity (position change / time)
284-
calculated_vel = (dx/dt, dy/dt, dz/dt)
295+
if velocity_is_zero and model_name in self.last_agent_positions and dt > 0:
296+
# Calculate velocity from position difference
297+
prev_pos = self.last_agent_positions[model_name]
298+
dx = position.x - prev_pos[0]
299+
dy = position.y - prev_pos[1]
300+
dz = position.z - prev_pos[2]
301+
302+
# Calculate velocity (position change / time)
303+
calculated_vel = (dx/dt, dy/dt, dz/dt)
304+
305+
# Apply some smoothing with the previous velocity if available
306+
if model_name in self.last_agent_velocities:
307+
prev_vel = self.last_agent_velocities[model_name]
308+
# Apply exponential smoothing (0.7 current + 0.3 previous)
309+
velocity = (
310+
0.7 * calculated_vel[0] + 0.3 * prev_vel[0],
311+
0.7 * calculated_vel[1] + 0.3 * prev_vel[1],
312+
0.7 * calculated_vel[2] + 0.3 * prev_vel[2]
313+
)
314+
else:
315+
velocity = calculated_vel
285316

286-
# Apply some smoothing with the previous velocity if available
287-
if model_name in self.last_agent_velocities:
288-
prev_vel = self.last_agent_velocities[model_name]
289-
# Apply exponential smoothing (0.7 current + 0.3 previous)
290-
velocity = (
291-
0.7 * calculated_vel[0] + 0.3 * prev_vel[0],
292-
0.7 * calculated_vel[1] + 0.3 * prev_vel[1],
293-
0.7 * calculated_vel[2] + 0.3 * prev_vel[2]
294-
)
317+
# Determine activity state based on velocity magnitude
318+
velocity_magnitude = np.linalg.norm(velocity)
319+
if velocity_magnitude < 0.1:
320+
activity = AgentActivityEnum.STOPPED
321+
elif velocity_magnitude > 5.0: # Arbitrary threshold for "fast"
322+
activity = AgentActivityEnum.FAST
295323
else:
296-
velocity = calculated_vel
324+
activity = AgentActivityEnum.MOVING
325+
326+
# Get agent dimensions
327+
dimensions = AGENT_DIMENSIONS.get(agent_type, (1.0, 1.0, 1.0)) # Default if unknown
297328

298-
# Determine activity state based on velocity magnitude
299-
velocity_magnitude = np.linalg.norm(velocity)
300-
if velocity_magnitude < 0.1:
301-
activity = AgentActivityEnum.STOPPED
302-
elif velocity_magnitude > 5.0: # Arbitrary threshold for "fast"
303-
activity = AgentActivityEnum.FAST
304-
else:
305-
activity = AgentActivityEnum.MOVING
329+
# Create agent state
330+
agent_state = AgentState(
331+
pose=agent_pose, # Using START frame pose
332+
dimensions=dimensions,
333+
outline=None,
334+
type=getattr(AgentEnum, agent_type.upper()),
335+
activity=activity,
336+
velocity=velocity,
337+
yaw_rate=angular_vel.z
338+
)
306339

307-
# Get agent dimensions
308-
dimensions = AGENT_DIMENSIONS.get(agent_type, (1.0, 1.0, 1.0)) # Default if unknown
309-
310-
# Create agent state
311-
agent_state = AgentState(
312-
pose=agent_pose, # Using START frame pose
313-
dimensions=dimensions,
314-
outline=None,
315-
type=getattr(AgentEnum, agent_type.upper()),
316-
activity=activity,
317-
velocity=velocity,
318-
yaw_rate=angular_vel.z
319-
)
320-
321-
# Store current position for next velocity calculation (using raw positions)
322-
self.last_agent_positions[model_name] = (position.x, position.y, position.z)
323-
self.last_agent_velocities[model_name] = velocity
324-
325-
# Call the callback with the agent state
326-
self.agent_detector_callback(model_name, agent_state)
340+
# Store current position for next velocity calculation (using raw positions)
341+
self.last_agent_positions[model_name] = (position.x, position.y, position.z)
342+
self.last_agent_velocities[model_name] = velocity
343+
# Call the callback with the agent state
344+
self.agent_detector_callback(model_name, agent_state)
345+
346+
if obstacle_type is not None:
347+
# Create obstacle state
348+
obstacle_state = ObstacleState(
349+
dimensions=(0,0,0),
350+
outline=None,
351+
pose=agent_pose,
352+
type=getattr(ObstacleMaterialEnum, obstacle_type.upper()),
353+
activity=ObstacleStateEnum.STANDING,
354+
)
355+
self.obstacle_detector_callback(model_name, obstacle_state)
327356

328357
def subscribe_sensor(self, name, callback, type=None):
329358
if name == 'gnss':
@@ -424,6 +453,11 @@ def callback_with_cv2(msg: Image):
424453
raise ValueError("GEMGazeboInterface only supports AgentState for agent_detector")
425454
self.agent_detector_callback = callback
426455

456+
elif name == 'obstacle_detector':
457+
if type is not None and type is not ObstacleState:
458+
raise ValueError("GEMGazeboInterface only supports ObstacleState for obstacle_detector")
459+
self.obstacle_detector_callback = callback
460+
427461
def hardware_faults(self) -> List[str]:
428462
# In simulation, we don't have real hardware faults
429463
return self.faults
@@ -459,10 +493,12 @@ def send_command(self, command : GEMVehicleCommand):
459493

460494
# Calculate acceleration from pedal positions
461495
acceleration = pedal_positions_to_acceleration(accelerator_pedal_position, brake_pedal_position, v, 0, 1)
462-
496+
print("acceleration before", acceleration)
463497
# Apply reasonable limits to acceleration
464498
max_accel = settings.get('vehicle.limits.max_acceleration', 1.0)
465-
max_decel = settings.get('vehicle.limits.max_deceleration', -2.0)
499+
max_decel = -1 * settings.get('vehicle.limits.max_deceleration', 2.0) # cuz ackermann expects neg but pure pursiut wants positive decel val
500+
print("max_accel", max_accel)
501+
print("max_decel", max_decel)
466502
acceleration = np.clip(acceleration, max_decel, max_accel)
467503

468504
# Convert wheel angle to steering angle (front wheel angle)
@@ -477,14 +513,15 @@ def send_command(self, command : GEMVehicleCommand):
477513
# Don't use infinite speed, instead calculate a reasonable target speed
478514
current_speed = v
479515
target_speed = current_speed
480-
516+
print("acceleration ", acceleration)
481517
if acceleration > 0:
482518
# Accelerating - set target speed to current speed plus some increment
483519
# This is more realistic than infinite speed
484520
max_speed = settings.get('vehicle.limits.max_speed', 10.0)
485521
target_speed = min(current_speed + acceleration * 0.5, max_speed)
486522
elif acceleration < 0:
487523
# Braking - set target speed to zero if deceleration is significant
524+
print("braking ", acceleration)
488525
if brake_pedal_position > 0.1:
489526
target_speed = 0.0
490527

@@ -499,4 +536,4 @@ def send_command(self, command : GEMVehicleCommand):
499536
print(f"[ACKERMANN] Speed: {msg.speed:.2f}, Accel: {msg.acceleration:.2f}, Steer: {msg.steering_angle:.2f}")
500537

501538
self.ackermann_pub.publish(msg)
502-
self.last_command = command
539+
self.last_command = command

GEMstack/state/obstacle.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from dataclasses import dataclass
1+
from __future__ import annotations
2+
from dataclasses import dataclass, replace
23
from ..utils.serialization import register
3-
from .physical_object import PhysicalObject
4+
from .physical_object import ObjectFrameEnum,PhysicalObject #,convert_vector
45
from enum import Enum
56

67
class ObstacleMaterialEnum(Enum):
@@ -16,10 +17,29 @@ class ObstacleMaterialEnum(Enum):
1617
SMALL_ANIMAL = 9
1718
ROADKILL = 10
1819

20+
class ObstacleStateEnum(Enum):
21+
STOPPED = 0 # standing pedestrians, parked cars, etc. No need to predict motion.
22+
MOVING = 1 # standard motion. Predictions will be used here
23+
FAST = 2 # indicates faster than usual motion
24+
UNDETERMINED = 3 # unknown activity
25+
STANDING = 4 # standing cone
26+
LEFT = 5 # flipped cone facing left
27+
RIGHT = 6 # flipped cone facing right
1928

2029
@dataclass
2130
@register
2231
class Obstacle(PhysicalObject):
2332
material : ObstacleMaterialEnum
2433
collidable : bool
2534

35+
36+
@dataclass
37+
@register
38+
class ObstacleState(PhysicalObject):
39+
type: ObstacleMaterialEnum
40+
activity: ObstacleStateEnum
41+
42+
def to_frame(self, frame: ObjectFrameEnum, current_pose=None, start_pose_abs=None) -> ObstacleState:
43+
newpose = self.pose.to_frame(frame, current_pose, start_pose_abs)
44+
# newvelocity = convert_vector(self.velocity, self.pose.frame, frame, current_pose, start_pose_abs)
45+
return replace(self, pose=newpose) #, velocity=newvelocity)

launch/fixed_route.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ variants:
8686
type: agent_detection.GazeboAgentDetector
8787
args:
8888
tracked_model_prefixes: ['pedestrian', 'car', 'bicycle']
89+
obstacle_detection:
90+
type: obstacle_detection.GazeboObstacleDetector
91+
args:
92+
tracked_obstacle_prefixes: ['cone']
8993
visualization: !include "mpl_visualization.yaml"
9094
log_ros:
9195
log:

0 commit comments

Comments
 (0)