Panther /tf and /tf_static topics are not namespaced in multi-robot setup

Hello Husarion team,

I am working with multiple real Husarion Panther UGVs in a ROS 2 multi-robot setup, not simulation.

The TF frames already have their own prefix per robot, which helps avoid frame-name collisions. However, on the real robot, the TF topics themselves are still global:

/tf
/tf_static

They do not become:

/<robot_namespace>/tf
/<robot_namespace>/tf_static

This creates complexity when multiple real robots share the same ROS 2 network. Even if the frames are prefixed, all robots still publish TF data on the same global topics.

My questions are:

  1. What is the recommended approach for handling Panther real-robot multi-robot TF?

  2. Should we rely only on frame prefixes, or is it supported/recommended to namespace /tf and /tf_static?

  3. Is there an easy way to switch between these two modes:

  • prefixed frames on namespaced /tf and /tf_static

  • namespaced /tf and /tf_staticwithout prefixed frames

  1. Can this be configured through Docker Compose/environment variables, or do we need to override the husarion_ugv_ros launch files and add the TF remapping manually?

This is important for us because we are building a real multi-robot system, and we need a clean and scalable TF architecture for multiple Panthers on the same network.

Thanks.

Hello @eldesouky,

Currently, the namespace behavior matches what you described. There is a single topic, /tf, and /tf_static, which contains the frames of all robots, preceded by the appropriate prefix.

This stems from the project’s roots, which evolved from ROS 1, where this approach was standard.

For a long time, ROS 2 didn’t provide any recommendations on how to handle tf with multiple robots, so this concept remained unchanged. Frankly, I haven’t been following this thread for a while, so I don’t know if there are official recommendations for this type of solution. However, I’m aware that many solutions use the /tf topic remapping approach.

Answering your questions:

  1. What is the recommended approach for handling a multi-robot TF structure in Panther?
    Setting the namespace in compose.yaml adds the namespace to all nodes and the appropriate prefix to the frame names. If a frame is a common source for all, such as a map, an appropriate map -> robot_odom transformation should be provided for each robot.

  2. Should we rely solely on frame prefixes, or is this supported/recommended in the /tf and /tf_static namespaces?
    This is not supported and would require rebuilding the husarion_ugv_ros rep with appropriate changes to the launch file, such as adding a function (SetRemap to globally set all absolute /tf topics to relative tf ).

  3. & 4. Is there a simple way to switch between these two modes?
    Currently, there is no such functionality.

Could you share your experience on why changing the approach from namespaced tf topics is better in your use cases? We could then analyzing the benefits and implementing this type of functionality on the development branch.

1 Like

Hello @Rafal,

Thanks for the clarification.

After reviewing other ROS 2 multi-robot approaches, especially the Open Robotics Discourse discussion about multi-robot TF trees and the Nav2 discussion about namespaced TFs, I think the main point is:

Frame prefixes solve frame-name collision, but they do not solve TF topic traffic isolation.

References:

Open Robotics Discourse: TF tree in a multi-robot setup in ROS 2
https://discourse.openrobotics.org/t/tf-tree-in-a-multi-robot-setup-in-ros2/41426

Nav2 issue: How should I interact with namespaced Nav2 TFs in Jazzy?
https://github.com/ros-navigation/navigation2/issues/5449

There is also a very relevant comment from Nav2 project lead Steve Macenski in the Nav2 issue:

“We remap the TF from the global namespace for all nodes into the robots’ individual namespaces so that they do not collide. In a realistic system, you wouldn’t want to have TF maintaining high rate data about every other robot, growing N^2.”

This matches our concern exactly: the issue is not only frame collision, but also high-rate TF traffic scaling and isolation.

In the Open Robotics Discourse discussion, one proposed approach is “multiple TF trees”, where each robot has its own TF topics:

/robot1/tf
/robot1/tf_static

/robot2/tf
/robot2/tf_static

Then, if a global or fleet-level TF view is needed, only selected transforms can be republished or bridged to a shared/global TF topic.

This is also close to how Nav2 multi-robot deployments commonly handle TF remapping: absolute /tf and /tf_static are remapped to relative tf and tf_static, so the namespace controls the final TF topic.

With the current Panther behavior:

/tf
/tf_static

all robots publish and consume TF through the same global topics. Even if the frames are prefixed correctly, every robot-local stack can still receive TF traffic from the other robots. This becomes harder to scale, debug, record with rosbag, filter, and isolate when multiple real robots are running on the same ROS 2 network.

The two modes we would like to support are:

1. Prefixed frames on namespaced TF topics

Example topics:

/robot1/tf
/robot1/tf_static

Example frames:

robot1/base_link
robot1/odom
robot1/front_laser

This gives both frame-name safety and TF topic isolation.

2. Namespaced TF topics without prefixed frames

Example topics:

/robot1/tf
/robot1/tf_static

Example frames:

base_link
odom
front_laser

This is useful when each robot stack is fully isolated inside its namespace, and any global/fleet TF view is handled separately by a bridge or coordination layer.

From our current investigation, this seems to require two launch-level changes.

A. Add global TF remapping in the bringup launch

As you mentioned, we would need to use SetRemap inside the bringup launch, preferably around the relevant nodes using a GroupAction, to force absolute TF topics to become relative:

SetRemap(src="/tf", dst="tf")
SetRemap(src="/tf_static", dst="tf_static")

B. Make frame_prefix configurable in load_urdf.launch.py

Currently, robot_state_publisher receives:

robot_state_pub_node = Node(
    package="robot_state_publisher",
    executable="robot_state_publisher",
    parameters=[
        {"robot_description": robot_description_content},
        {"frame_prefix": namespace_ext},
    ],
    namespace=namespace,
    emulate_tty=True,
)

To support switching between prefixed and non-prefixed frames, frame_prefix would need to be controlled by a launch argument or environment variable instead of always being tied to namespace_ext.

In conclusion, the current prefix-based design is valid for avoiding frame-name conflicts, but namespaced TF topics provide better traffic isolation, cleaner per-robot debugging, cleaner rosbag recording, and better separation between local robot TF and global/fleet TF.

For us, the important point is to avoid maintaining a fork of husarion_ugv_ros, because this is the robot’s basic driver layer. We want to keep receiving upcoming Husarion updates, support, and Docker image releases without losing compatibility or maintaining separate custom Docker images.

If you are open to supporting this in the official branch, we can also propose or collaborate on the solution.

Add this to the thread:

I also want to add another related real-robot case to this discussion.

We are integrating a Fixposition Vision-RTK 2 onto a Panther running ROS 2 Jazzy using fixposition_driver_ros2, and we have encountered a TF namespacing issue. We would appreciate your guidance before implementing a custom solution.

Setup

Panther publishes its TF tree under a namespace/prefix as shipped, for example:

panther1/base_link

panther1/odom

…

The Fixposition driver, however, publishes TF frames as global, non-namespaced names, including:

vrtk_link

odom

FP_ECEF

FP_POI

FP_VRTK

FP_ENU0

…

We verified from the Fixposition documentation that there is no support for tf_prefix / frame_prefix; namespacing applies only to topics.

We also reviewed the Husarion Fixposition demo repository:

It appears that the setup does not support applying a frame prefix to Fixposition TF frames either.

Problem

This results in two odom frames in /tf:

panther1/odom

odom

These represent different physical/logical frames, but TF consumers such as Nav2, robot_localization, and RViz can interpret or configure them ambiguously.

Additionally, all Fixposition frames exist outside the panther1/ namespace, which is problematic for multi-robot setups.

This connects directly to the previous TF discussion: frame prefixes and TF-topic namespacing need to be configurable independently. Some integrations need prefixed frames, while others need namespaced TF topics and controlled bridging/rewriting of selected transforms.

Question

Have you previously integrated Fixposition Vision-RTK 2 with Panther, or with other Husarion robots, while maintaining proper TF namespacing for frames?

If yes, what is the recommended approach?

Hi @eldesouky,

Personally, I’m for option A. As for the decision on which option we’ll choose and when, I’ll let you know tomorrow (I need to discuss your observations).

1 Like

As promised, I’m back with an answer. Next week, I could prepare a developer version with a namespace. However, I’ve been working on ROSbot for the past few months and don’t currently have Panther for physical testing. So I’d appreciate your feedback once the branch is ready and simulation-tested.

Regarding the fix position, the issue isn’t fully resolved. My colleague’s findings are presented here: https://github.com/husarion/husarion_outdoor_nav_ros/issues/392#issuecomment-4182495189

1 Like

Thank you for the follow-up.

That would be very helpful. If you can prepare a developer branch with TF topic namespacing support, we can test it on real Panther robots and provide feedback from a physical multi-robot setup.

This will help validate the behavior beyond simulation, especially real network traffic and TF isolation in a multi-robot system. We will wait for the developer branch and hold our custom changes for now, so we can test the official approach first.

Regarding the Fixposition issue, the link you shared does not open from my side. Could you please confirm if it is public or share the relevant findings here?

Hi @eldesouky,

Thank you for your willingness to test it on a physical robot.
I’ll let you know when I start working on this project. I will most likely close the currently open projects around Wednesday.

Fixposition
The link is actually not public. I’m copying my colleague’s comment:

There are 2 approaches to integrate it with our stack. We can use just fixposition as a standalone sensor for positioning. Or we can use data published by it and provide map → odom transform (with ekf for example). Second approach allows to add more sensors when needed.

Standalone

  • Disable lokal ekf in the driver, tis is the most problematic for dynamic switching. FP provides it’s own odom frame which would cause confilcts with our current setup
  • tf vrtk_link to lynx/base_link (weird to have tf from sensor to base_link, but that how it works)
  • Optional fork of the repo to make it possible to assign different frames than map and odom (without tf_prefix)
  • Adjust nav2 to use config without tf_prefix ORRR add static tf’s map → lynx/map, odom → lynx/odom

Custom map → odom TF with Fixposition data

  1. first approach - EKF + provided transforms
  • Disable FP_ECEF transform publishing
  • tf map → FP_ENU0
  • tf base_link → FP_POI
  • ekf with single fixposition subscription, optionally can add additional info
  1. second approach - EKF (or not) + ignore FP transforms
  • node, that subscribes to fixposition/odometry_enu, and republishes it changing frames to vrtk_link and map,
  • tf base_link (mount_link) → vrtk_link
  • this node could also publish TF map → odom (this can be enabled/disabled by param if needed), if we dont want to use EKF for more inputs.

I did a PoC with second approach. It works and output is identical to our custom setup with GPS. Others approaches would require access to hardware. I’m not continuing until we decide which approach is the best.

1 Like

Hi @RafalGorecki,

Thank you for the clarification and for sharing the findings.

We will wait for the TF namespacing developer branch. This is currently blocking our multi-robot integration, so we will hold our custom solution and test the developer branch once it is ready.

Regarding Fixposition, the standalone approach also aligns with one of the TF modes we requested: namespaced /tf and /tf_static topics without frame prefixes. In that mode, the robot can keep TF traffic isolated under the robot namespace, while the frame names can remain unprefixed, such as map, odom, and base_link.

So the TF changes would help not only with multi-robot TF isolation, but also with integrations like Fixposition where removing the frame prefix may be needed.

Thanks again.