diff --git a/.github/workflows/happypose_ros_build_and_test.yaml b/.github/workflows/happypose_ros_build_and_test.yaml new file mode 100644 index 0000000..a2c9150 --- /dev/null +++ b/.github/workflows/happypose_ros_build_and_test.yaml @@ -0,0 +1,70 @@ +name: "Humble: Build and Test" + +on: [ push, pull_request ] + +jobs: + test_happypose_ros: + runs-on: ubuntu-22.04 + + env: + HAPPYPOSE_DATA_DIR: /tmp/local_data + + steps: + - name: Install EGL mesa - required for Panda3D renderer + run: | + sudo apt-get update + sudo apt-get install -qqy libegl1-mesa libegl1-mesa-dev + + - name: Install Python C headers and remove Blinker version conflicting with HappyPose + run: | + sudo apt-get update + sudo apt-get install -qqy python3-dev + sudo apt purge -qqy python3-blinker + + - name: Caching of the HappyPose installation and data + uses: actions/cache@v4 + with: + path: /tmp/local_data + key: data + + - name: Update pip + run: pip install -U pip + + - name: Download HappyPose source + working-directory: /tmp + run: | + git clone --branch dev --recurse-submodules https://github.com/agimus-project/happypose.git + + - name: Build and install HappyPose + working-directory: /tmp/happypose + run: pip install ".[cpu,pypi,evaluation,multiview]" --extra-index-url https://download.pytorch.org/whl/cpu + + - name: Download pre-trained models required for tests + run: | + mkdir -p /tmp/local_data + python -m happypose.toolbox.utils.download \ + --bop_dataset ycbv \ + --cosypose_models \ + detector-bop-ycbv-pbr--970850 \ + coarse-bop-ycbv-pbr--724183 \ + refiner-bop-ycbv-pbr--604090 + + - name: Unzip HappyPose YCBV models + working-directory: /tmp/local_data/bop_datasets/ycbv + run: | + unzip -n -qq ycbv_base.zip + unzip -n -qq ycbv_models.zip + + - name: Remove incompatible PyTest version + run: pip uninstall -y pytest + + - name: Install ROS 2 Humble + uses: ros-tooling/setup-ros@v0.7 + with: + required-ros-distributions: humble + + - name: Build and test happypose_ros + uses: ros-tooling/action-ros-ci@v0.3 + with: + package-name: happypose_ros + target-ros2-distro: humble diff --git a/README.md b/README.md index f643648..c2e5ce0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,19 @@ + # happypose_ros + +[![License](https://img.shields.io/badge/License-BSD_2--Clause-orange.svg)](https://opensource.org/licenses/BSD-2-Clause) +[![Test happypose_ros](https://github.com/agimus-project/happypose_ros/actions/workflows/happypose_ros_build_and_test.yaml/badge.svg)](https://github.com/agimus-project/happypose_ros/actions/workflows/happypose_ros_build_and_test.yaml +) +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/agimus-project/happypose_ros/main.svg)](https://results.pre-commit.ci/latest/github/agimus-project/happypose_ros/main) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + ROS 2 wrapper for a 6D pose estimation library, [Happypose](https://github.com/agimus-project/happypose). ## Build instructions -:warning: Conda installation is not supported +> [!WARNING] +> Conda installation is not supported Currently, there is no automated build for happypose library itself built into the ROS node. Please follow the installation guide found in the [happypose README.md](https://github.com/agimus-project/happypose?tab=readme-ov-file#example-with-venv). @@ -17,11 +26,14 @@ colcon build --symlink-install ## Launch -:warning: Intrinsic parameters of the camera are approximate in the demos and may cause inaccurate results! You can change them by modifying the `k_matrix` param in [cosypose_params.yaml](./happypose_examples/config/cosypose_params.yaml) file. +> [!NOTE] +> Intrinsic parameters of the camera are approximate in the demos and may cause inaccurate results! You can change them by modifying the `k_matrix` param in [cosypose_params.yaml](./happypose_examples/config/cosypose_params.yaml) file. -:warning: When running the demos, make sure to change `device` parameter according to your hardware configuration! +> [!TIP] +> When running the demos, make sure to change `device` parameter according to your hardware configuration! -Before running the demos download the dataset model and pretrained detectors for object type you plan to use. The default dataset used in the examples is ycbv. For more information refer to [*Downloading and preparing the data* page](https://agimus-project.github.io/happypose/cosypose/download_data.html) in the HappyPose documentation. +> [!IMPORTANT] +> Before running the demos download the dataset model and pretrained detectors for object type you plan to use. The default dataset used in the examples is ycbv. For more information refer to [*Downloading and preparing the data* page](https://agimus-project.github.io/happypose/cosypose/download_data.html) in the HappyPose documentation. To launch the demo run: ```bash diff --git a/happypose_ros/test/single_view_base.py b/happypose_ros/test/single_view_base.py index 4351a30..20ebfbf 100644 --- a/happypose_ros/test/single_view_base.py +++ b/happypose_ros/test/single_view_base.py @@ -99,9 +99,9 @@ def test_03_trigger_pipeline(self, proc_output: ActiveIoHandler) -> None: assert ready, "Failed to trigger the pipeline!" def test_04_receive_messages(self) -> None: - self.node.assert_message_received("happypose/detections", timeout=20.0) - self.node.assert_message_received("happypose/markers", timeout=2.0) - self.node.assert_message_received("happypose/vision_info", timeout=2.0) + self.node.assert_message_received("happypose/detections", timeout=180.0) + self.node.assert_message_received("happypose/markers", timeout=6.0) + self.node.assert_message_received("happypose/vision_info", timeout=6.0) def test_05_check_vision_info(self) -> None: vision_info = self.node.get_received_message("happypose/vision_info") diff --git a/happypose_ros/test/test_multi_view_integration.py b/happypose_ros/test/test_multi_view_integration.py index ae19150..d36a1e5 100644 --- a/happypose_ros/test/test_multi_view_integration.py +++ b/happypose_ros/test/test_multi_view_integration.py @@ -137,8 +137,8 @@ def test_05_trigger_pipeline(self, proc_output: ActiveIoHandler) -> None: self.fail("Failed to trigger the pipeline!") def test_06_receive_messages(self) -> None: - self.node.assert_message_received("happypose/detections", timeout=20.0) - self.node.assert_message_received("happypose/vision_info", timeout=2.0) + self.node.assert_message_received("happypose/detections", timeout=180.0) + self.node.assert_message_received("happypose/vision_info", timeout=8.0) def test_07_check_vision_info(self) -> None: vision_info = self.node.get_received_message("happypose/vision_info") @@ -244,11 +244,11 @@ def test_12_dynamic_params_camera_no_timeout(self) -> None: # Disable timeout. Single image should trigger now self.set_timeout(0.0) self.node.publish_image("cam_1", self.cam_1_image, self.K) - self.node.assert_message_received("happypose/detections", timeout=20.0) + self.node.assert_message_received("happypose/detections", timeout=180.0) def expect_no_detection(self) -> None: with self.assertRaises(AssertionError) as excinfo: - self.node.assert_message_received("happypose/detections", timeout=5.0) + self.node.assert_message_received("happypose/detections", timeout=60.0) self.assertTrue( "No messages received" in str(excinfo.exception), msg="One image after timeout triggered the pipeline!", @@ -284,7 +284,7 @@ def test_15_dynamic_params_camera_timeout_three_cameras_ok(self) -> None: self.node.publish_image("cam_1", self.cam_1_image, self.K) self.node.publish_image("cam_2", self.cam_2_image, self.K) self.node.publish_image("cam_3", self.cam_3_image, self.K) - self.node.assert_message_received("happypose/detections", timeout=20.0) + self.node.assert_message_received("happypose/detections", timeout=60.0) def test_16_dynamic_params_camera_timeout_three_cameras_short(self) -> None: # Set timeout to a small value @@ -315,7 +315,7 @@ def test_17_dynamic_params_camera_timeout_three_cameras_short_ok(self) -> None: self.node.publish_image("cam_1", self.cam_1_image, self.K) self.node.publish_image("cam_2", self.cam_2_image, self.K) self.node.publish_image("cam_3", self.cam_3_image, self.K) - self.node.assert_message_received("happypose/detections", timeout=20.0) + self.node.assert_message_received("happypose/detections", timeout=60.0) def setup_timestamp_test( self, offsets: List[float], expected: float, strategy: str @@ -350,7 +350,7 @@ def setup_timestamp_test( self.set_timeout(0.0) self.node.publish_image("cam_3", self.cam_3_image, self.K, cam_3_stamp) # Await the results - self.node.assert_message_received("happypose/detections", timeout=20.0) + self.node.assert_message_received("happypose/detections", timeout=60.0) detections = self.node.get_received_message("happypose/detections") stamp_sec = (Time.from_msg(detections.header.stamp) - now).nanoseconds / S_TO_NS