-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* update to newest metadrive * rename package * update * update rviz * update readme * update package name * update package name * reformat use indent=4space * update readme * fix dependency and install instruction * update readme * test is successful with ros humble * add test * fix test * remove source * remove source line * add python dependency * clean the code * clean the code; run isort and yapf * add python dependency * remove container * debug * remove pip install -e .for test * use container * use container * install gym outside the pip * stop using container * remove init * fix init * no update and init * source * source * use sudo rosdep * disable plugin * fix bug server * restore vision plugins * remove line * use default python * test! * bypass doc string check when merging to main --------- Co-authored-by: QuanyiLi <quanyili0057@gmail.com>
- Loading branch information
1 parent
c4d926e
commit 486f190
Showing
17 changed files
with
859 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Connecting MetaDrive Simulator with ROS2 | ||
|
||
We provide an example bridge for connecting MetaDrive and ROS2 using zmq sockets, which publishes messages for camera | ||
images, LiDAR point clouds, and object 3D bounding boxes. | ||
|
||
## Installation | ||
|
||
To install the bridge, first [install ROS2 **humble**](https://docs.ros.org/en/humble/Installation.html) and follow the scripts | ||
below: | ||
|
||
```bash | ||
cd bridges/ros_bridge | ||
|
||
# activate env, ${ROS_DISTRO} is something like foxy, iron, humble | ||
source /opt/ros/${ROS_DISTRO}/setup.bash | ||
# You may need to run init, if you are installing ros2 for the first time | ||
sudo rosdep init | ||
# zmq should be installed with system python interpreter | ||
pip install pyzmq | ||
rosdep update | ||
rosdep install --from-paths src --ignore-src -y | ||
|
||
# build | ||
colcon build | ||
source install/setup.bash | ||
``` | ||
|
||
## Usage | ||
|
||
```bash | ||
# Terminal 1, launch ROS publishers | ||
ros2 launch metadrive_example_bridge metadrive_example_bridge.launch.py | ||
# Terminal 2, launch socket server | ||
python ros_socket_server.py | ||
|
||
``` | ||
|
||
[Demo Video](https://www.youtube.com/watch?v=WWwdnURnOBM&list=TLGGdRGbC4RGzhAxNzEwMjAyMw) | ||
|
||
|
||
## Known Issues | ||
If you are using the `conda`, it is very likely that the interpreter will not match the system interpreter | ||
and will be incompatible with ROS 2 binaries. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
""" | ||
Only for github CI using! The visualization tool is not available on github server, remove it. | ||
""" | ||
import os | ||
|
||
|
||
def delete_line_from_file(file_name, line_number): | ||
# Check if the line number is valid | ||
if line_number < 1: | ||
return | ||
|
||
lines = [] | ||
with open(file_name, 'r') as f: | ||
lines = f.readlines() | ||
|
||
# Check if the line number is beyond the file's length | ||
if line_number > len(lines): | ||
return | ||
|
||
with open(file_name, 'w') as f: | ||
for idx, line in enumerate(lines): | ||
if idx + 1 != line_number: | ||
f.write(line) | ||
|
||
|
||
# Usage | ||
if __name__ == '__main__': | ||
delete_line_from_file(os.path.join(os.path.dirname(__file__), 'src', "metadrive_example_bridge", 'package.xml'), 10) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# launch sockets to send sensor readings to ROS | ||
import argparse | ||
import struct | ||
|
||
import numpy as np | ||
import zmq | ||
|
||
from metadrive.constants import HELP_MESSAGE | ||
from metadrive.engine.asset_loader import AssetLoader | ||
from metadrive.envs.scenario_env import ScenarioEnv | ||
from metadrive.policy.replay_policy import ReplayEgoCarPolicy | ||
|
||
|
||
class RosSocketServer(): | ||
|
||
def __init__(self): | ||
self.context = zmq.Context().instance() | ||
self.context.setsockopt(zmq.IO_THREADS, 2) | ||
self.img_socket = self.context.socket(zmq.PUSH) | ||
self.img_socket.setsockopt(zmq.SNDBUF, 4194304) | ||
self.img_socket.bind("ipc:///tmp/rgb_camera") | ||
self.img_socket.set_hwm(5) | ||
self.lidar_socket = self.context.socket(zmq.PUSH) | ||
self.lidar_socket.setsockopt(zmq.SNDBUF, 4194304) | ||
self.lidar_socket.bind("ipc:///tmp/lidar") | ||
self.lidar_socket.set_hwm(5) | ||
self.obj_socket = self.context.socket(zmq.PUSH) | ||
self.obj_socket.setsockopt(zmq.SNDBUF, 4194304) | ||
self.obj_socket.bind("ipc:///tmp/obj") | ||
self.obj_socket.set_hwm(5) | ||
|
||
def run(self, test=False): | ||
config = dict( | ||
use_render=True if not test else False, # need this on to get the camera | ||
num_scenarios=1, | ||
horizon=1000, | ||
image_observation=True if not test else False, | ||
manual_control=False, | ||
agent_policy=ReplayEgoCarPolicy, | ||
rgb_clip=False, | ||
show_logo=False, | ||
show_fps=False, | ||
show_interface=False, | ||
physics_world_step_size=0.02, | ||
vehicle_config=dict( | ||
image_source="main_camera", | ||
show_navi_mark=False, | ||
), | ||
data_directory=AssetLoader.file_path("nuscenes", return_raw_style=False), | ||
) | ||
|
||
env = ScenarioEnv(config) | ||
|
||
try: | ||
|
||
env.reset() | ||
print(HELP_MESSAGE) | ||
env.vehicle.expert_takeover = False | ||
while True: | ||
o = env.step([0, 0]) | ||
if test: | ||
image_data = np.zeros((512, 512, 3)) # fake data for testing | ||
image_data[::16, :, :] = 255 | ||
else: | ||
image_data = o[0]['image'][..., -1] | ||
# send via socket | ||
image_data = image_data.astype(np.uint8) | ||
dim_data = struct.pack('ii', image_data.shape[1], image_data.shape[0]) | ||
image_data = bytearray(image_data) | ||
# concatenate the dimensions and image data into a single byte array | ||
image_data = dim_data + image_data | ||
try: | ||
self.img_socket.send(image_data, zmq.NOBLOCK) | ||
except zmq.error.Again: | ||
msg = "ros_socket_server: error sending image" | ||
if test: | ||
raise ValueError(msg) | ||
else: | ||
print(msg) | ||
del image_data # explicit delete to free memory | ||
|
||
lidar_data, objs = env.vehicle.lidar.perceive( | ||
env.vehicle, | ||
env.engine.physics_world.dynamic_world, | ||
env.vehicle.config["lidar"]["num_lasers"], | ||
env.vehicle.config["lidar"]["distance"], | ||
height=1.0, | ||
) | ||
|
||
ego_x = env.vehicle.position[0] | ||
ego_y = env.vehicle.position[1] | ||
ego_theta = np.arctan2(env.vehicle.heading[1], env.vehicle.heading[0]) | ||
|
||
num_data = struct.pack('i', len(objs)) | ||
obj_data = [] | ||
for obj in objs: | ||
obj_x = obj.position[0] | ||
obj_y = obj.position[1] | ||
obj_theta = np.arctan2(obj.heading[1], obj.heading[0]) | ||
|
||
obj_x = obj_x - ego_x | ||
obj_y = obj_y - ego_y | ||
obj_x_new = np.cos(-ego_theta) * obj_x - np.sin(-ego_theta) * obj_y | ||
obj_y_new = np.sin(-ego_theta) * obj_x + np.cos(-ego_theta) * obj_y | ||
|
||
obj_data.append(obj_x_new) | ||
obj_data.append(obj_y_new) | ||
obj_data.append(obj_theta - ego_theta) | ||
obj_data.append(obj.LENGTH) | ||
obj_data.append(obj.WIDTH) | ||
obj_data.append(obj.HEIGHT) | ||
obj_data = np.array(obj_data, dtype=np.float32) | ||
obj_data = bytearray(obj_data) | ||
obj_data = num_data + obj_data | ||
try: | ||
self.obj_socket.send(obj_data, zmq.NOBLOCK) | ||
except zmq.error.Again: | ||
msg = "ros_socket_server: error sending objs" | ||
if test: | ||
raise ValueError(msg) | ||
else: | ||
print(msg) | ||
del obj_data # explicit delete to free memory | ||
|
||
# convert lidar data to xyz | ||
lidar_data = np.array(lidar_data) * env.vehicle.config["lidar"]["distance"] | ||
lidar_range = env.vehicle.lidar._get_lidar_range( | ||
env.vehicle.config["lidar"]["num_lasers"], env.vehicle.lidar.start_phase_offset | ||
) | ||
point_x = lidar_data * np.cos(lidar_range) | ||
point_y = lidar_data * np.sin(lidar_range) | ||
point_z = np.ones(lidar_data.shape) # assume height = 1.0 | ||
lidar_data = np.stack([point_x, point_y, point_z], axis=-1).astype(np.float32) | ||
dim_data = struct.pack('i', len(lidar_data)) | ||
lidar_data = bytearray(lidar_data) | ||
# concatenate the dimensions and lidar data into a single byte array | ||
lidar_data = dim_data + lidar_data | ||
try: | ||
self.lidar_socket.send(lidar_data, zmq.NOBLOCK) | ||
except zmq.error.Again: | ||
msg = "ros_socket_server: error sending lidar" | ||
if test: | ||
raise ValueError(msg) | ||
else: | ||
print(msg) | ||
del lidar_data # explicit delete to free memory | ||
|
||
if o[2]: # done | ||
break | ||
|
||
except Exception as e: | ||
import traceback | ||
traceback.print_exc() | ||
raise e | ||
|
||
finally: | ||
env.close() | ||
|
||
|
||
def main(test=False): | ||
server = RosSocketServer() | ||
server.run(test) | ||
|
||
|
||
if __name__ == "__main__": | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument("--test", action="store_true") | ||
args = parser.parse_args() | ||
main(args.test) |
26 changes: 26 additions & 0 deletions
26
bridges/ros_bridge/src/metadrive_example_bridge/launch/metadrive_example_bridge.launch.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import os | ||
|
||
from ament_index_python.packages import get_package_share_directory | ||
from launch import LaunchDescription | ||
from launch_ros.actions import Node | ||
|
||
|
||
def generate_launch_description(): | ||
ld = LaunchDescription() | ||
camera_node = Node(package="metadrive_example_bridge", executable="camera_bridge", name='camera_bridge') | ||
ld.add_action(camera_node) | ||
lidar_node = Node(package="metadrive_example_bridge", executable="lidar_bridge", name='lidar_bridge') | ||
ld.add_action(lidar_node) | ||
obj_node = Node(package="metadrive_example_bridge", executable="obj_bridge", name='obj_bridge') | ||
ld.add_action(obj_node) | ||
|
||
pkg_dir = get_package_share_directory('metadrive_example_bridge') | ||
rviz_node = Node( | ||
package='rviz2', executable='rviz2', name='rviz2', arguments=['-d', [os.path.join(pkg_dir, 'default.rviz')]] | ||
) | ||
ld.add_action(rviz_node) | ||
return ld | ||
|
||
|
||
if __name__ == '__main__': | ||
generate_launch_description() |
Empty file.
Oops, something went wrong.