Johnliu/optitrack autonomy#359
Open
JohnYanxinLiu wants to merge 23 commits into
Open
Conversation
…os and linux versions
… Hand test in mocap room successful
…est. Unit tests workflows created
Contributor
There was a problem hiding this comment.
Pull request overview
Adds optional OptiTrack/NatNet motion-capture support to the robot perception stack and expands the testing infrastructure by separating fast unit tests from Docker-based system tests, plus adding CI/build tooling and Docker profile improvements.
Changes:
- Introduces
natnet_ros2(C++ NatNet node + Python vision pose converter), launch/config files, and an SDK download/install helper with licensing gate. - Restructures pytest into
tests/system/+tests/robot/proxy-based unit tests (@pytest.mark.unit), adds a YAML-drivencolcon testmanifest, and adds/updates system tests. - Updates robot Docker build/profile plumbing (ROS_DISTRO args, Jetson L4T stack-base image, compose wiring) and documentation/nav to reflect the new workflows.
Reviewed changes
Copilot reviewed 67 out of 69 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/system/test_takeoff_hover_land.py | New PX4 takeoff/hover/land system test chain with per-velocity ordering and metrics capture |
| tests/system/test_sensors.py | Fixes import path for liveliness helpers after moving into tests/system/ |
| tests/system/test_liveliness.py | Updates docstring reference to new sensors module path |
| tests/system/test_build_packages.py | Adds colcon test step gated by YAML package list |
| tests/system/test_build_docker.py | New system test for building images and recording image sizes |
| tests/system/init.py | Makes tests/system importable as a package (dotted module names) |
| tests/sim/README.md | Adds structure guidance for sim-side unit tests |
| tests/sim/motive_emulator/README.md | Plans for a future NatNet wire-protocol emulator integration test |
| tests/sensor_probes.py | Updates docs and fixes LiDAR validation script path |
| tests/robot/sensors/README.md | Documents unit test proxy layout for sensors layer |
| tests/robot/sensors/lidar_point_cloud_filter/test_validation_core.py | Pytest proxy that re-exports package-local LiDAR unit tests |
| tests/robot/README.md | Documents the co-location + proxy testing pattern |
| tests/robot/perception/README.md | Documents unit test proxy layout for perception layer |
| tests/robot/perception/natnet_ros2/test_natnet_ros2.py | Pytest proxy that re-exports natnet_ros2 Python unit tests |
| tests/robot/local/README.md | Documents unit test proxy layout for local layer |
| tests/robot/interface/README.md | Documents unit test proxy layout for interface layer |
| tests/robot/global/README.md | Documents unit test proxy layout for global layer |
| tests/robot/behavior/README.md | Documents unit test proxy layout for behavior layer |
| tests/requirements.txt | Adds PyYAML and NumPy for new test/config parsing |
| tests/README.md | Major update: clarifies system vs unit tests, paths, marks, and examples |
| tests/pytest.ini | Registers unit mark and keeps test discovery rooted at tests/ |
| tests/parse_metrics.py | Updates node-id parsing to support system.* module prefix |
| tests/conftest.py | Adds YAML-driven colcon test config + module ordering (unit before system) |
| tests/colcon_unit_test_packages.yaml | New manifest of ROS packages gated by colcon test in CI |
| robot/ros_ws/src/sensors/sensor_interfaces/package.xml | Reorders/adjusts dependency entries (adds sensor_msgs) |
| robot/ros_ws/src/sensors/lidar_point_cloud_filter/test/test_validation_core.py | Adds NumPy-only unit tests for LiDAR validation rules |
| robot/ros_ws/src/sensors/lidar_point_cloud_filter/setup.py | Switches from tests_require to extras_require['test'] |
| robot/ros_ws/src/sensors/lidar_point_cloud_filter/setup.cfg | Adds pytest configuration for package-local test discovery |
| robot/ros_ws/src/sensors/lidar_point_cloud_filter/scripts/validate_lidar_filter_clouds.py | Refactors validation into reusable pure-numeric helper module |
| robot/ros_ws/src/sensors/lidar_point_cloud_filter/README.md | Updates docs for new test layout and validation_core usage |
| robot/ros_ws/src/sensors/lidar_point_cloud_filter/lidar_point_cloud_filter/validation_core.py | New pure-numeric validation helpers (no ROS imports) |
| robot/ros_ws/src/perception/perception_bringup/launch/perception.launch.xml | Adds launch_natnet arg and conditional include for NatNet launch |
| robot/ros_ws/src/perception/natnet_ros2/test/test_natnet_ros2.py | Adds ROS-stubbed Python unit tests for vision pose converter logic |
| robot/ros_ws/src/perception/natnet_ros2/test/test_natnet_logic.cpp | Adds extensive gtests for SDK-independent NatNet logic and seams |
| robot/ros_ws/src/perception/natnet_ros2/test/fake_natnet_client.hpp | Adds in-process NatNet client test double for unit tests |
| robot/ros_ws/src/perception/natnet_ros2/src/vision_pose_converter_node.py | Adds Python node bridging pose_cov to MAVROS vision_pose topics |
| robot/ros_ws/src/perception/natnet_ros2/src/natnet_ros2_node.cpp | Adds C++ NatNet SDK ROS2 node publishing Pose/PoseWithCovariance |
| robot/ros_ws/src/perception/natnet_ros2/src/natnet_client_adapter.cpp | Adds SDK adapter implementation (only TU including NatNet headers) |
| robot/ros_ws/src/perception/natnet_ros2/scripts/download-natnet-sdk.sh | Adds SDK downloader/installer with explicit license acceptance flow |
| robot/ros_ws/src/perception/natnet_ros2/README.md | Adds module docs (usage, topics, config, troubleshooting) |
| robot/ros_ws/src/perception/natnet_ros2/package.xml | New ROS 2 package manifest for natnet_ros2 |
| robot/ros_ws/src/perception/natnet_ros2/launch/vision_pose_converter.launch.xml | Launches converter node with remaps to NatNet + MAVROS topics |
| robot/ros_ws/src/perception/natnet_ros2/launch/natnet_ros2.launch.py | Launches NatNet node; conditionally includes MAVROS bridge |
| robot/ros_ws/src/perception/natnet_ros2/include/natnet_ros2/natnet_logic.hpp | New pure-logic header (covariance, topic names, negotiation seam) |
| robot/ros_ws/src/perception/natnet_ros2/include/natnet_ros2/natnet_client_adapter.hpp | Declares NatNet SDK adapter without including SDK headers |
| robot/ros_ws/src/perception/natnet_ros2/env-hooks/natnet_library_path.dsv.in | Adds LD_LIBRARY_PATH hook for NatNet shared library |
| robot/ros_ws/src/perception/natnet_ros2/config/vision_pose_converter.yaml | Params for converter (frame IDs, canonical quaternion) |
| robot/ros_ws/src/perception/natnet_ros2/config/natnet_config.yaml | ROS 2 parameter file for NatNet connection/body/publish settings |
| robot/ros_ws/src/perception/natnet_ros2/CMakeLists.txt | Conditional build/install based on SDK presence; installs hooks/tests |
| robot/ros_ws/src/perception/natnet_ros2/.gitignore | Ignores proprietary SDK artifacts in-tree |
| robot/ros_ws/src/local/controls/pid_controller_msgs/package.xml | Reorders interface package metadata entry |
| robot/docker/zed/Dockerfile.zed-l4t | Fixes GeoGraphicLib dev package name for L4T build |
| robot/docker/Dockerfile.robot | Adds ROS_DISTRO/PYTHON_VERSION args and refactors ROS package installs |
| robot/docker/Dockerfile.l4t-stack-base | New Jetson intermediary base image to normalize ROS/Python environment |
| robot/docker/docker-compose.yaml | Wires LAUNCH_NATNET env; adds L4T stack-base service and build args |
| mkdocs.yml | Adds testing docs sections and NatNet module doc link |
| docs/development/intermediate/testing/unit_testing.md | New guide for unit-test proxy pattern and CI workflow |
| docs/development/intermediate/testing/index.md | Updates testing overview (unit vs package vs system) |
| docs/development/docker-build-profiles.md | New doc on docker build profiles/build-args |
| common/ros_packages/msgs/task_msgs/package.xml | Reorders interface package metadata entry |
| common/ros_packages/msgs/airstack_msgs/package.xml | Reorders interface package metadata entry |
| airstack.sh | Adds --no-natnet setup option; ensures robot-l4t stack-base prebuild |
| AGENTS.md | Updates test documentation and adds new agent skill references |
| .env | Bumps version to 0.19.0-alpha.1 |
| .agents/skills/run-system-tests/SKILL.md | Updates skill docs for new tests/system/ structure and unit tests |
| .agents/skills/docker-build-profiles/SKILL.md | Adds build-profile guidance and validation snippets |
| .agents/skills/configure-multi-robot/SKILL.md | Updates references to new system test path |
| .agents/skills/add-unit-tests/SKILL.md | New skill documenting co-location + proxy unit test workflow |
| .agents/skills/add-ros2-package/assets/package_template/setup.py | Updates template to use extras_require['test'] |
Comments suppressed due to low confidence (1)
robot/ros_ws/src/perception/natnet_ros2/src/natnet_client_adapter.cpp:169
set_frame_callback()stores the callback intl_frame_cband registerssdk_frame_callbackwith a null context. If multiple adapters are constructed or the callback fires on a different thread, this can dispatch to the wrong callback or none at all. Prefer registeringsdk_frame_callbackwiththisas the context and dispatching to a member-stored callback via that context pointer.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+45
to
+52
| // We store the user callback per-adapter instance in a thread-local to avoid | ||
| // a global. Limitation: only one adapter instance per thread (sufficient for | ||
| // the single-node use case). | ||
| thread_local std::function<void(const FrameSample &)> * tl_frame_cb = nullptr; | ||
|
|
||
| void NATNET_CALLCONV sdk_frame_callback(sFrameOfMocapData * data, void * /*ctx*/) | ||
| { | ||
| if (!data || !tl_frame_cb || !*tl_frame_cb) { return; } |
Comment on lines
+20
to
+28
| ``` | ||
| Motive (External PC) | ||
| ↓ NatNet UDP (port 1511) | ||
| ↓ | ||
| NatNet ROS 2 Node | ||
| ├→ /robot_1/perception/optitrack/{body_name} (PoseStamped) | ||
| ├→ /robot_1/perception/optitrack/pose_cov (PoseWithCovarianceStamped) | ||
| └→ (Optional) /robot_1/mavros/vision_pose/pose (for PX4) | ||
| ``` |
Comment on lines
+62
to
+82
| natnet: | ||
| # Motive PC network settings | ||
| server_ip: "192.168.1.1" # IP of Motive PC | ||
| data_port: 1511 # Local UDP port bound for NatNet data stream | ||
|
|
||
| # Rigid body tracking | ||
| body_names: | ||
| - "quad_1" | ||
| - "marker_cloud_1" | ||
|
|
||
| # Publishing behavior | ||
| publish_direct_optitrack: true # Publish individual body topics | ||
| publish_to_mavros: false # Bridge to MAVROS for PX4 | ||
|
|
||
| # Frame IDs | ||
| frame_id: "world" | ||
| child_frame_id: "base_link" | ||
|
|
||
| # Covariance (default uncertainty) | ||
| position_covariance: [0.1, 0.0, 0.0, ...] | ||
| orientation_covariance: [0.01, 0.0, 0.0, ...] |
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What features did you add and/or bugs did you address?
GitHub issue:
Adds native OptiTrack motion capture integration to AirStack via the NatNet SDK.
The
natnet_ros2package receives rigid body pose data from an external Motive PCover NatNet UDP and publishes into the AirStack perception layer as standard ROS 2
PoseStamped/PoseWithCovarianceStampedtopics, with an optional MAVROS bridgefor PX4 external pose feedback.
Also included: unit testing infrastructure improvements — pytest registration fixes
for
lidar_point_cloud_filter, a YAML-driven colcon test manifest(
tests/colcon_unit_test_packages.yaml), and ~55 C++ gtests + 7 Python unit testsfor natnet logic.
How did you implement it?
C++ NatNet node (
natnet_ros2_node.cpp): connects to Motive via the NatNet SDK,registers a frame callback that decodes rigid body poses, and publishes them namespaced
under
/{ROBOT_NAME}/perception/optitrack/{body_name}.Python vision pose converter (
vision_pose_converter_node.py): subscribes to theraw optitrack pose and re-publishes as
/{ROBOT_NAME}/mavros/vision_pose/posewithcanonical quaternion normalization for PX4.
Opt-in by default:
LAUNCH_NATNETdefaults tofalseindocker-compose.yaml(
LAUNCH_NATNET=${LAUNCH_NATNET:-false}). SetLAUNCH_NATNET=truein.envtoactivate. If
truebut the SDK was not installed, the launch file raises aRuntimeError(fail-hard rather than silently skip).Conditional build:
CMakeLists.txtdetects the SDK at configure time. If absent,the
natnet_ros2_nodeexecutable is skipped with a warning — the package still buildscleanly for CI without the SDK.
SDK licensing: The OptiTrack NatNet SDK is proprietary and not redistributed.
Users install it locally via
airstack setup(explicit license acceptance). AirStackremains fully open-source.
LD_LIBRARY_PATH: An ament env-hook (
natnet_library_path.dsv.in) adds the SDK.sotoLD_LIBRARY_PATHonswsso no manual path configuration is needed.Unit test isolation: C++ logic (frame decoding, covariance matrix construction,
topic name building) is separated into a thin adapter class so it can be tested
without the real SDK or a live ROS node. Tests live in
robot/ros_ws/src/perception/natnet_ros2/test/test_natnet_logic.cpp.YAML test manifest:
tests/colcon_unit_test_packages.yamllists which packagesare gated under
colcon testin CI. This replaces a hardcoded list intest_build_packages.pyand makes it easy to add new packages without touching thetest harness.
How do you run and use it?
To install the NatNet SDK, run
airstack setupYou can launch airstack with NatNet enabled:
AUTOLAUNCH=true; LAUNCH_NATNET=true; airstack upFor now, this will only launch the natnet application without connecting to any server. Soon, an Isaac-Sim emulator will be implemented to give this more confidence. For now, NatNet is not launched by defualt.
(Listed above)
Testing with PyTest
What pytests did you add to ensure the feature is reliable and robust? What metrics are used?
Unit tests for lidar_filter_cloud are reorganized, and unit tests regarding the helper functions and coordinate framing for optitrack are added.
What's the exact command to run the pytests that test your feature? i.e.
airstack test -m ...To test python-based unit tests, run
airstack test -m unit -vTo run C++ unit tests and building, run
airstack test -m build_packages -k test_colcon_test_robot -vWhat are the expected results of the tests? What should a maintainer look at to understand whether the test succeeded?
All tests should pass.
Documentation
Was mkdocs.yml updated? (y/n)
y
Do the docs have sufficient scope such that a newcomer can easily reproduce and use your feature?
yes, the structure of the unit tests are decided and documented in the development procedure. SKILLS directory is updated with templates for new agents to contribute.
Is there sufficient visual media?
N/A
Versioning
.envfile according to semantic versioning?yes