├── .gitmodules ├── README.md ├── realsense-ros └── ros.Dockerfile └── tests ├── ros_tests.py └── run_tests.sh /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "orbslam3"] 2 | path = orbslam3 3 | url = https://github.com/open-shade/orbslam3.git 4 | branch = main 5 | [submodule "yolov5"] 6 | path = yolov5 7 | url = https://github.com/open-shade/yolov5.git 8 | branch = main 9 | [submodule "swin-classification"] 10 | path = swin-classification 11 | url = https://github.com/open-shade/swin-classification.git 12 | branch = master 13 | [submodule "convnext"] 14 | path = convnext 15 | url = https://github.com/open-shade/convnext.git 16 | branch = master 17 | [submodule "segformer"] 18 | path = segformer 19 | url = https://github.com/open-shade/segformer.git 20 | branch = master 21 | [submodule "deit"] 22 | path = deit 23 | url = https://github.com/open-shade/deit.git 24 | branch = master 25 | [submodule "ghostnet"] 26 | path = ghostnet 27 | url = https://github.com/open-shade/ghostnet.git 28 | branch = master 29 | [submodule "template-semantic-segmentation"] 30 | path = template-semantic-segmentation 31 | url = https://github.com/open-shade/template-semantic-segmentation.git 32 | branch = main 33 | [submodule "template-object-detection"] 34 | path = template-object-detection 35 | url = https://github.com/open-shade/template-object-detection.git 36 | branch = master 37 | [submodule "template-image-classification"] 38 | path = template-image-classification 39 | url = https://github.com/open-shade/template-image-classification.git 40 | branch = master 41 | [submodule "detr_segmentation"] 42 | path = detr_segmentation 43 | url = https://github.com/open-shade/detr_segmentation.git 44 | branch = main 45 | [submodule "detr_detection"] 46 | path = detr_detection 47 | url = https://github.com/open-shade/detr_detection.git 48 | branch = master 49 | [submodule "ssd"] 50 | path = ssd 51 | url = https://github.com/open-shade/ssd.git 52 | branch = master 53 | [submodule "beit_segmentation"] 54 | path = beit_segmentation 55 | url = https://github.com/open-shade/beit_segmentation.git 56 | branch = main 57 | [submodule "segformer_segmentation"] 58 | path = segformer_segmentation 59 | url = https://github.com/open-shade/segformer_segmentation.git 60 | branch = main 61 | [submodule "dpt_segmentation"] 62 | path = dpt_segmentation 63 | url = https://github.com/open-shade/dpt_segmentation.git 64 | branch = main 65 | [submodule "yolos"] 66 | path = yolos 67 | url = https://github.com/open-shade/yolos.git 68 | branch = master 69 | [submodule "segformer_classification"] 70 | path = segformer_classification 71 | url = https://github.com/open-shade/segformer_classification.git 72 | branch = master 73 | [submodule "vision_perceiver"] 74 | path = vision_perceiver 75 | url = https://github.com/open-shade/vision_perceiver.git 76 | branch = master 77 | [submodule "vision_transformer"] 78 | path = vision_transformer 79 | url = https://github.com/open-shade/vision_transformer.git 80 | branch = master 81 | [submodule "swin"] 82 | path = swin 83 | url = https://github.com/open-shade/swin.git 84 | branch = master 85 | [submodule "resnet"] 86 | path = resnet 87 | url = https://github.com/open-shade/resnet.git 88 | branch = master 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # We're migrating popular and useful ROS1 packages to ROS2 while also containerizing everything 2 | 3 | I think a lot of people who have tried to start a project using ROS2 are familiar with the pain of trying to find compatible packages. For example, running SLAM on a Jetson Nano using ROS2 is often a difficult setup - finding compatible ROS2 wrappers, compiling on ARM and then maintaining/upgrading the stack is often a game of luck between what compiles and what ROS version you're using. 4 | 5 | Someone already nailed the state of ROS2 open source with [this meme](https://www.reddit.com/r/ROS/comments/u249az/now_is_the_time_to_migrate_your_ros_1_package_to/?utm_source=share&utm_medium=web2x&context=3). 6 | 7 | On our team, we've been working in ROS2 and feel this pain frequently, this is why we've been steadily putting together a large port of algorithms including tons of **pre-trained ML algos**, new ROS2 wrappers like **a ROS2 wrapper for ORBSLAM3** and very common robotics algos like **YOLOv5.** 8 | 9 | Almost everything we've created is compatible with 10 | 11 | - Foxy 12 | - Galactic 13 | - Humble 14 | - Rolling 15 | 16 | Everything is already containerized and (almost everything) is compiled for - 17 | 18 | - linux/amd64 19 | - linux/arm/v7 20 | - linux/arm/v8 21 | - linux/arm64 22 | 23 | **All ML models are also built with Nvidia drivers for GPU acceleration.** 24 | 25 | This allows ROS2 to be totally plug and play. Now when our team wants to run YOLOv5 on a Jetson Nano using GPU acceleration on humble we just `docker run -it shaderobotics/yolov5:humble` (or just run it natively using the build steps). 26 | 27 | All of it is open source of course so you can build and hack it however you want - each repo follows the same license set by the authors of the the original repo. 28 | 29 | We have no interest in monetization, we just want to make robotics development faster and easier. This project has already saved our team tons of time so hopefully it can save you time too. 30 | 31 | If there is interest in this project, our hope is that this could be a community effort for porting old/dead/not containerized projects to the ROS2 ecosystem. If you find it useful, dropping a star on the /algos repo really helps gauge interest. 32 | 33 | Would love to hear any thoughts on this - Is this a bad way of doing it? How should we make sure the original authors get the citations? Are we duplicating other work we don't know about? -------------------------------------------------------------------------------- /realsense-ros/ros.Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=ubuntu:16.04 2 | ################################# 3 | # Librealsense Builder Stage # 4 | ################################# 5 | FROM $BASE_IMAGE 6 | 7 | ARG LIBRS_VERSION=2.50.0 8 | # Make sure that we have a version number of librealsense as argument 9 | RUN test -n "$LIBRS_VERSION" 10 | 11 | # To avoid waiting for input during package installation 12 | ENV DEBIAN_FRONTEND=noninteractive 13 | 14 | # Builder dependencies installation 15 | RUN apt-get update \ 16 | && apt-get install -qq -y --no-install-recommends \ 17 | build-essential \ 18 | cmake \ 19 | git \ 20 | libssl-dev \ 21 | libusb-1.0-0-dev \ 22 | pkg-config \ 23 | libgtk-3-dev \ 24 | libglfw3-dev \ 25 | libgl1-mesa-dev \ 26 | libglu1-mesa-dev \ 27 | curl \ 28 | python3 \ 29 | python3-dev \ 30 | ca-certificates \ 31 | && rm -rf /var/lib/apt/lists/* 32 | 33 | # Download sources 34 | WORKDIR /usr/src 35 | RUN curl https://codeload.github.com/IntelRealSense/librealsense/tar.gz/refs/tags/v$LIBRS_VERSION -o librealsense.tar.gz 36 | RUN tar -zxf librealsense.tar.gz \ 37 | && rm librealsense.tar.gz 38 | RUN ln -s /usr/src/librealsense-$LIBRS_VERSION /usr/src/librealsense 39 | 40 | # Build and install 41 | RUN cd /usr/src/librealsense \ 42 | && mkdir build && cd build \ 43 | && cmake \ 44 | -DCMAKE_C_FLAGS_RELEASE="${CMAKE_C_FLAGS_RELEASE} -s" \ 45 | -DCMAKE_CXX_FLAGS_RELEASE="${CMAKE_CXX_FLAGS_RELEASE} -s" \ 46 | -DCMAKE_INSTALL_PREFIX=/opt/librealsense \ 47 | -DBUILD_GRAPHICAL_EXAMPLES=OFF \ 48 | -DBUILD_PYTHON_BINDINGS:bool=true \ 49 | -DCMAKE_BUILD_TYPE=Release ../ \ 50 | && make -j$(($(nproc)-1)) all \ 51 | && make install 52 | 53 | # Copy binaries from builder stage 54 | RUN cp -r /opt/librealsense/. /usr/local/ 55 | 56 | ENV PYTHONPATH=$PYTHONPATH:/usr/local/lib 57 | 58 | # Install dep packages 59 | RUN apt-get update \ 60 | && apt-get install -y --no-install-recommends \ 61 | libusb-1.0-0 \ 62 | udev \ 63 | apt-transport-https \ 64 | ca-certificates \ 65 | curl \ 66 | software-properties-common \ 67 | && rm -rf /var/lib/apt/lists/* 68 | 69 | # Shows a list of connected Realsense devices 70 | CMD [ "rs-enumerate-devices", "--compact" ] 71 | 72 | 73 | ###################################### 74 | # Librealsense ROS builder stage # 75 | ###################################### 76 | 77 | RUN apt update && apt install -y curl dpkg unzip && \ 78 | sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' && \ 79 | curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | apt-key add - && \ 80 | apt update && \ 81 | apt install -y ros-kinetic-desktop-full 82 | 83 | 84 | RUN apt install -y ros-kinetic-realsense2-camera 85 | 86 | 87 | # Usage 88 | # roslaunch realsense2_camera rs_camera.launch 89 | -------------------------------------------------------------------------------- /tests/ros_tests.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import threading 3 | import rclpy 4 | import time 5 | import os 6 | import requests 7 | from rclpy.node import Node 8 | import subprocess 9 | 10 | from posix import _exit 11 | 12 | from std_msgs.msg import String 13 | 14 | MAX_INIT_ATTEMPTS = 10 15 | 16 | # shaderobotics/resnet 17 | # Try to get the "resnet" component of any algorithm. This is used to identify that the topics have launched 18 | ALGO_NICKNAME = os.getenv('ALGO').split('/')[1] 19 | 20 | ALGO_DETAILS = requests.get('https://provisioning.shaderobotics.com/v1/algo', params={'algo': os.getenv('ALGO')}).json() 21 | 22 | 23 | def eprint(msg): 24 | print(msg, file=sys.stderr) 25 | 26 | 27 | class MinimalSubscriber(Node): 28 | def __init__(self): 29 | super().__init__('testing_node') 30 | # Thread kill flags 31 | self.subprocess = None 32 | 33 | # Wait 10 seconds for the algorithm to initialize 34 | attempts = 0 35 | while True: 36 | def check_if_algorithm_launched() -> bool: 37 | for topic in self.get_topic_names_and_types(): 38 | if ALGO_NICKNAME in topic[0]: 39 | return True 40 | return False 41 | 42 | if check_if_algorithm_launched(): 43 | break 44 | 45 | attempts += 1 46 | if attempts >= MAX_INIT_ATTEMPTS: 47 | raise TimeoutError(f"Could not find a topic matching {ALGO_NICKNAME} in {MAX_INIT_ATTEMPTS} seconds.") 48 | time.sleep(1) 49 | 50 | def determine_input_topic(): 51 | # Determine the algorithm's input topic 52 | try: 53 | topics: dict = ALGO_DETAILS['topics'] 54 | except KeyError: 55 | raise KeyError(f"Could not find 'topics' - found '{ALGO_DETAILS}'") 56 | for topic, value in topics.items(): 57 | if value['side'] == 'subscriber': 58 | return topic 59 | 60 | raise LookupError("Could not determine algorithm input topic") 61 | 62 | def determine_output_topic(): 63 | # Determine the algorithm's output topic 64 | publisher_list = [] 65 | try: 66 | topics: dict = ALGO_DETAILS['topics'] 67 | except KeyError: 68 | raise KeyError(f"Could not find 'topics' - found '{ALGO_DETAILS}'") 69 | for topic, value in topics.items(): 70 | if value['side'] == 'publisher': 71 | publisher_list.append(topic) 72 | 73 | for topic in publisher_list: 74 | if 'detections' in topic: 75 | return topic 76 | 77 | if len(publisher_list) > 0: 78 | return publisher_list[0] 79 | 80 | raise LookupError("Could not determine algorithm output topic") 81 | 82 | algorithm_input_topic = determine_input_topic() 83 | algorithm_output_topic = determine_output_topic() 84 | 85 | eprint(f"Algorithm input topic: {algorithm_input_topic} - algorithm output topic {algorithm_output_topic}") 86 | 87 | self.subscription = self.create_subscription( 88 | String, 89 | algorithm_output_topic, 90 | self.listener_callback, 91 | 2 92 | ) 93 | 94 | eprint(f"Subscribed to {algorithm_output_topic}") 95 | 96 | threading.Thread(target=self.start_publisher, args=(algorithm_input_topic, )).start() 97 | 98 | threading.Thread(target=self.kill_in_time, args=(MAX_INIT_ATTEMPTS, )).start() 99 | 100 | def kill_proc(self, exit_code: int): 101 | if self.subprocess is not None: 102 | self.subprocess.kill() 103 | _exit(exit_code) 104 | 105 | def start_publisher(self, input_topic): 106 | self.subprocess = subprocess.Popen(['python3', os.getcwd() + '/camera_simulator.py'], 107 | env={**dict(os.environ), **{"OUTPUT_TOPIC": input_topic}}) 108 | 109 | def kill_in_time(self, seconds_to_live: int): 110 | time.sleep(seconds_to_live) 111 | eprint(f"Did not hear response in {seconds_to_live}") 112 | self.kill_proc(1) 113 | 114 | def listener_callback(self, msg): 115 | def check_all_topic_types(): 116 | active_topics = self.get_topic_names_and_types() 117 | required_topics = ALGO_DETAILS['topics'] 118 | for topic in required_topics: 119 | correct = False 120 | for active_topic in active_topics: 121 | # When one of the active topics match a required topic 122 | if topic == active_topic[0]: 123 | # Now also check type 124 | if active_topic[1][0] in required_topics[topic]['type']: 125 | eprint(f'✅ {active_topic[0]} found and has correct type ' 126 | f'{required_topics[active_topic[0]]["type"]}') 127 | correct = True 128 | break 129 | 130 | if not correct: 131 | eprint(f"❌ Could not find {topic} of the correct name and type in {active_topics}") 132 | self.kill_proc(1) 133 | 134 | return True 135 | 136 | check_all_topic_types() 137 | 138 | eprint("✅ Validated algorithm - exiting...") 139 | 140 | self.kill_proc(0) 141 | 142 | 143 | def main(args=None): 144 | rclpy.init(args=args) 145 | 146 | minimal_subscriber = MinimalSubscriber() 147 | 148 | rclpy.spin(minimal_subscriber) 149 | 150 | # Destroy the node explicitly 151 | # (optional - otherwise it will be done automatically 152 | # when the garbage collector destroys the node object) 153 | minimal_subscriber.destroy_node() 154 | rclpy.shutdown() 155 | 156 | 157 | if __name__ == '__main__': 158 | main() 159 | -------------------------------------------------------------------------------- /tests/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure the video folder exists 4 | mkdir -p /root 5 | 6 | # Install the deps to run the video streamer 7 | apt update && \ 8 | apt install -y \ 9 | python3-natsort \ 10 | curl \ 11 | python3-pip ffmpeg libsm6 libxext6 ros-"${ROS_DISTRO}"-cv-bridge ros-"${ROS_DISTRO}"-vision-opencv ros-"${ROS_DISTRO}"-rclpy && \ 12 | python3 -m pip install --upgrade pip && python3 -m pip install opencv-python && \ 13 | curl https://sample-videos.com/video123/mp4/480/big_buck_bunny_480p_10mb.mp4 --output /root/video.mp4 14 | 15 | curl https://raw.githubusercontent.com/open-shade/algos/main/tests/ros_tests.py --output ./ros_tests.py 16 | curl https://raw.githubusercontent.com/open-shade/video_simulator/master/camera_simulator/camera_simulator.py --output ./camera_simulator.py 17 | 18 | python3 -m pip install requests 19 | 20 | echo "Starting the ROS algo" 21 | /home/shade/shade_ws/start.sh & 22 | 23 | source /opt/ros/"${ROS_DISTRO}"/setup.sh 24 | source ./install/setup.sh 25 | 26 | echo "Starting testing suite" 27 | python3 ./ros_tests.py 28 | result=$? 29 | # sudo python3 ./ros_tests.py 30 | 31 | echo "Killing the ROS algo" 32 | kill -9 $! 33 | 34 | exit "$result" 35 | --------------------------------------------------------------------------------