5.5.4 Data Visualization

Foxglove Visualization

Use Foxglove Studio to visualize robot topic data in real time.

The robot runs foxglove_bridge to publish topic data via WebSocket; the host PC runs the Foxglove client to subscribe and visualize. Both must be on the same network.

Install Foxglove

Install Foxglove Desktop or use the Web version on the host PC; either can be used with any account:

Note

When using the web version to connect to the robot (plain ws:// connection), the browser may block the connection due to security policies; in that case, select Enable insecure scripts in the top-right corner of the page.

Enable insecure scripts

Launch foxglove_bridge

Launch foxglove_bridge on the development computing unit (PC2, 10.0.1.41; pre-installed in the environment):

# Source environment variables
source /opt/ros/humble/setup.bash
source ~/aimdk/install/setup.bash

# Launch foxglove_bridge (default configuration)
ros2 launch foxglove_bridge foxglove_bridge_launch.xml

Connect to the Robot

In the Foxglove Desktop / Web version on the host PC, open the console and select Open connection.

Open connection

Open Foxglove, click Open connection and select Foxglove WebSocket. Replace localhost in the default address ws://localhost:8765 with the robot’s IP, then click Open to establish the connection.

WebSocket connection

Add panel

After connecting, select Add panel in the top-right corner and choose the appropriate panel for the target topic – e.g., the 3D panel for LiDAR point cloud, or the transform tree for TF relationships.

Add panel

Topic Settings

Click a panel to select its visualization settings on the left, such as the topic and reference frame.

Topic visualization

Import Layout

Instead of adding panels manually, you can import a preset layout directly (URDF display is limited to the Desktop version). Click the Layout icon in the top-right corner of Foxglove:

Layout icon

Import the configuration file below (replace $URDF_DIR in the file with the actual local URDF path before use): foxglove_layout.json

foxglove_layout.json (click to expand)
{
  "configById": {
    "Image!3uaqfl": {
      "cameraState": {
        "distance": 20,
        "perspective": true,
        "phi": 60,
        "target": [
          0,
          0,
          0
        ],
        "targetOffset": [
          0,
          0,
          0
        ],
        "targetOrientation": [
          0,
          0,
          0,
          1
        ],
        "thetaOffset": 60,
        "fovy": 45,
        "near": 0.5,
        "far": 5000
      },
      "followMode": "follow-pose",
      "scene": {},
      "transforms": {},
      "topics": {},
      "layers": {},
      "publish": {
        "type": "point",
        "poseTopic": "/move_base_simple/goal",
        "pointTopic": "/clicked_point",
        "poseEstimateTopic": "/initialpose",
        "poseEstimateXDeviation": 0.5,
        "poseEstimateYDeviation": 0.5,
        "poseEstimateThetaDeviation": 0.26179939
      },
      "synchronize": false,
      "imageMode": {
        "imageTopic": "/aima/hal/sensor/rgb_head_front_center/rgb_image/compressed",
        "calibrationTopic": "/aima/hal/sensor/rgb_head_front_center/camera_info"
      }
    },
    "TransformTree!1x6xlz3": {},
    "3D!4h2shil": {
      "cameraState": {
        "distance": 7.169718448166827,
        "perspective": true,
        "phi": 51.81818181819366,
        "target": [
          0,
          0,
          0
        ],
        "targetOffset": [
          1.925555282550379,
          -0.29000302203140244,
          -3.7627255213825705e-17
        ],
        "targetOrientation": [
          0,
          0,
          0,
          1
        ],
        "thetaOffset": 111.27272727272965,
        "fovy": 45,
        "near": 0.5,
        "far": 5000
      },
      "followMode": "follow-pose",
      "scene": {
        "transforms": {
          "visible": false
        },
        "ignoreColladaUpAxis": true,
        "meshUpAxis": "z_up"
      },
      "transforms": {},
      "topics": {
        "/aima/hal/sensor/lidar_chest_front/lidar_pointcloud": {
          "visible": true,
          "colorField": "intensity",
          "colorMode": "colormap",
          "colorMap": "turbo"
        },
        "/perception/debug/obstacle_cluster": {
          "visible": false
        },
        "/aima/hal/sensor/rgb_head_rear/camera_info": {
          "visible": false
        },
        "/aima/hal/sensor/rgb_head_front_center/rgb_image/compressed": {
          "visible": false,
          "cameraInfoTopic": "/aima/hal/sensor/rgb_head_front_center/camera_info"
        }
      },
      "layers": {
        "grid": {
          "visible": true,
          "drawBehind": false,
          "instanceId": "d3834cc3-4c3e-40a9-8929-a318e6037634",
          "layerId": "foxglove.Grid",
          "size": 10,
          "divisions": 10,
          "lineWidth": 1,
          "color": "#248eff",
          "position": [
            0,
            0,
            0
          ],
          "rotation": [
            0,
            0,
            0
          ]
        },
        "44530a1b-44f8-4bf8-beb2-0a9e2df95e96": {
          "controlMode": "transforms",
          "jointStatesTopic": "",
          "displayMode": "auto",
          "fallbackColor": "#ffffff",
          "showAxis": false,
          "axisScale": 1,
          "showOutlines": true,
          "opacity": 1,
          "visible": true,
          "instanceId": "44530a1b-44f8-4bf8-beb2-0a9e2df95e96",
          "layerId": "foxglove.Urdf",
          "label": "URDF",
          "sourceType": "filePath",
          "url": "",
          "filePath": "$URDF_DIR/x2_ultra.urdf",
          "parameter": "",
          "topic": "",
          "framePrefix": "",
          "order": 1
        }
      },
      "publish": {
        "type": "point",
        "poseTopic": "/move_base_simple/goal",
        "pointTopic": "/clicked_point",
        "poseEstimateTopic": "/initialpose",
        "poseEstimateXDeviation": 0.5,
        "poseEstimateYDeviation": 0.5,
        "poseEstimateThetaDeviation": 0.26179939
      },
      "synchronize": false,
      "imageMode": {},
      "followTf": "base_link"
    }
  },
  "globalVariables": {
    "globalVariable": 3
  },
  "userNodes": {},
  "playbackConfig": {
    "speed": 1
  },
  "layout": {
    "direction": "row",
    "first": {
      "first": "Image!3uaqfl",
      "second": "TransformTree!1x6xlz3",
      "direction": "column"
    },
    "second": "3D!4h2shil"
  }
}

This layout simultaneously displays LiDAR point cloud, URDF, interaction camera, and TF live data:

Display after importing the layout

For more configuration options, see the Foxglove documentation.

Mapping and Relocalization

Perception topics such as elevation map and dynamic obstacles depend on mapping and relocalization being ready. Prerequisites:

  1. The robot must be in stable standing mode.

  2. The following apps must be running (they usually start automatically with the system; verify with ros2 node list):

    • slam — Mapping and relocalization

    • map_manager — Map management

    • perception — Perception

    • pnc — Planning and control

    • auto_integrated — Navigation integration

Warning

Do not enter the autonomous development mode in developer mode, otherwise the above apps may not start automatically, resulting in no data on perception topics.

If the prerequisites are not met, the relevant topics will publish no data. You can verify as follows:

# Check if nodes are running
ros2 node list | grep -E "slam|perception|pnc"

# Check if topics have data (should show continuous output)
ros2 topic hz /perception/grid_map

Perception Visualization Topics

Topic Name

Message Type

Description

QoS

Frequency

/perception/debug/left_fisheye_detection_viz

sensor_msgs/Image

Left fisheye image instance segmentation visualization (for debugging, to understand what the robot recognizes)

RELIABLE+VOLATILE

10Hz

/perception/debug/obstacle_semantic

sensor_msgs/PointCloud2

Semantic point cloud (obstacle avoidance input; distinguishes traversable / obstacle / pedestrian)

RELIABLE+VOLATILE

10Hz

/perception/grid_map

grid_map_msgs/GridMap

Elevation map (terrain traversability; identifies steps / pits / slopes)

RELIABLE+VOLATILE

10Hz

/perception/debug/obstacle_polygon

visualization_msgs/MarkerArray

Dynamic obstacles (dynamic obstacle avoidance; tracks moving people / objects)

RELIABLE+VOLATILE

10Hz

All topics QoS: history=keep_last, depth=10, reliability=reliable, durability=volatile

Coordinate System

Robot component frames follow the ROS standard FLU convention (X forward, Y left, Z up). See Coordinate System. This chapter involves two key frames:

Frame

Meaning

Characteristics

map

Global world frame, established during SLAM mapping

Fixed; Z axis points up, x/y in the horizontal plane; used by elevation map and dynamic obstacles

gravity_aligned_base_link

Robot body frame (gravity-aligned)

Translates and rotates with the robot; X axis points forward, Y axis points left, Z axis points up; used by semantic point cloud

Relationship: map is the fixed world reference frame; gravity_aligned_base_link moves with the robot. The robot’s position and orientation in map can be queried via the TF transform map gravity_aligned_base_link, for example:

# Query the robot's pose in the global map (translation=position, rotation=orientation)
ros2 run tf2_ros tf2_echo map gravity_aligned_base_link

Impact on visualization data:

  • Coordinates of topics such as elevation map and dynamic obstacles are relative to map; their directions are fixed in the world and do not change with the robot’s heading.

  • Coordinates of the semantic point cloud are relative to gravity_aligned_base_link; they change with the robot’s heading (the X axis always points forward).

  • To convert coordinates between the two frames, use TF (tf2_ros / tf2_geometry_msgs).

Image Instance Segmentation Visualization

Visualizes instance segmentation results on the left fisheye image, including class names, confidence scores, bounding boxes, and segmentation masks.

Image Instance Segmentation Visualization

Semantic Point Cloud

Point cloud within the current sensor FOV. frame_id is gravity_aligned_base_link (robot body frame, X axis points forward, see Coordinate System above). Colors are used to distinguish semantic categories (BGR values):

Semantic Category

BGR

Color

Traversable Area

[237, 177, 32]

Blue

Static Obstacle

[126, 47, 142]

Purple

Pedestrian

[119, 172, 48]

Green

Colors are stored in the rgb field of PointCloud2 (a float32 bit-cast to uint32 in 0x00RRGGBB format). For the unpacking method, see ROS2 sensor_msgs_py/point_cloud2.py.

Semantic Point Cloud

Elevation Map

Elevation grid map generated from current sensor input, frame_id is map (frame origin and orientation are fixed; see Coordinate System above), resolution 0.05 m, size 8 m × 8 m (160 × 160 cells). The map is a sliding window that follows the robot: each frame centers the window on the robot’s position (info.pose.position is the robot’s position in map), so the robot is always at the center of the map. The actual coverage is approximately ±4 m around the robot; cell data outside the window is discarded. Note that what translates is the map window (visible area), not the coordinate frame itself — cell coordinates are always absolute map coordinates and do not change with robot movement. Contains three layers:

Layer

Message Type

Content

elevation_map

float

Cell height value (z in map frame, unit m), fused from sensor point clouds frame by frame; unobserved cells are NaN

variance_map

float

Elevation variance, reflecting the uncertainty of the cell’s height estimate; unobserved cells are NaN

semantic_map

float

Semantic type, takes SemanticType enum values: traversable area=1, static obstacle=2, pedestrian=3, other obstacle=4; unobserved cells are NaN

The semantic value of each cell is accumulated from multiple observations: new observations update the cell’s semantic confidence, and cells unobserved for a long time gradually lose confidence. Message parsing uses GridMapRosConverter::fromMessage; see the grid_map_ros official documentation.

Elevation Map

Dynamic Obstacles

Each obstacle corresponds to 4 markers, distinguished by namespace (ns) and type (type):

Marker namespace (ns)

Type (type)

Content

cylinder

CYLINDER (3)

Obstacle center position, stored in marker.pose.position

arrow

ARROW (0)

Obstacle velocity: direction stored in marker.pose.orientation, speed magnitude stored in marker.scale.x

text

TEXT_VIEW_FACING (9)

Obstacle ID and speed magnitude, stored in marker.text

polygon

LINE_STRIP (4)

2D bounding polygon vertices, stored in marker.points (last vertex repeats the first to close the polygon)

The 4 markers of each obstacle share the same color, assigned automatically by obstacle ID. This differs from the semantic point cloud, which is colored by category.

The markers array of each MarkerArray message is constructed in the following order:

  1. First, 4 markers with action = DELETEALL (3) are appended, one for each of the above namespaces, to clear the previous frame

  2. Then, for each obstacle, 4 markers with action = ADD (0) are appended in order (cylinderarrowtextpolygon) to redraw the current frame

Thus each message contains a complete frame; subscribers can simply replace with the latest frame without cross-frame accumulation. The topic refreshes at 10 Hz. All markers’ header.frame_id is map (global world frame; see Coordinate System above), and header.stamp is the current frame timestamp. Unlike the elevation map, dynamic obstacle coordinates are absolute map coordinates and do not translate or clip with robot movement — any obstacle observed by the sensors is placed in map; the robot’s position is not encoded in the message (query via TF).

In the image, the thin cylinders represent obstacle center points (cylinder markers), and the outer polygons represent obstacle outlines (polygon markers).

Dynamic Obstacles

RSView Visualization

RSView is a LiDAR data visualization tool provided by RoboSense. It displays E1R point cloud data in real time and also supports offline playback from .pcap files.

Software Features

  1. Real-time data display over Ethernet

  2. Save live data recordings as PCAP files

  3. Playback from recorded PCAP files (.pcapng format not supported)

  4. Multiple visualization modes: distance, time, horizontal angle, reflectivity, laser beam ID, etc.

  5. Display point data in table format

  6. Export point cloud data as CSV files

  7. Distance measurement tool

  8. Display multiple consecutive frames of playback data simultaneously

  9. Crop display

Install RSView

RSView runs on Windows 64-bit and Ubuntu 18.04 and above.

Download the latest version from the RoboSense website: https://www.robosense.cn/resources-160

No installation required; extract the archive and run the executable. Do not use paths containing Chinese characters.

Online Mode

Attention

On the integrated X2 robot, RSView online mode is unavailable – use Foxglove / RViz instead to view the point cloud in real time. There are two reasons:

  • Unicast: the E1R point cloud on the robot is unicast, sent only to the robot itself (e.g., 10.11.1.1); other computers on the same subnet cannot receive this data.

  • Ports occupied: the MSOP / DIFOP ports (6699 / 7788) are exclusively bound by the robot’s HAL driver, so RSView cannot bind to them.

The correct way to view the point cloud in real time: the HAL driver converts the LiDAR UDP data into ROS topics in real time. Simply subscribe in the Foxglove 3D panel (or RViz) – same source data, same real-time performance, zero changes, no disruption to the robot:

/aima/hal/sensor/lidar_chest_front/lidar_pointcloud   # sensor_msgs/PointCloud2

See the Add Panel / Import Layout sections in “Foxglove Visualization” above for usage. For offline analysis of recorded data only, see “Offline Playback (PCAP files)” below.

Offline Playback

  1. Capture packets on the LiDAR network interface using dumpcap:

dumpcap -i sensor0 -f "udp port 6699 or udp port 7788" \
    -w ~/lidar_capture.pcap
  1. In RSView, select File → Open PCAP File from the top-left menu, choose the recorded .pcap file, and select the corresponding sensor type for playback.

RSView offline playback

RViz Visualization

RViz subscribes to robot topics directly via DDS, enabling real-time observation of the robot model, motion behavior, TF, LiDAR point cloud, maps, and more.

Attention

RViz relies on direct DDS network connectivity and currently only supports wired connections (no VPN / wireless). See Network Connection for network port configuration.

After completing the wired connection per Network Connection and setting ROS_DOMAIN_ID to match the robot, launch RViz2:

source /opt/ros/humble/setup.bash
rviz2

TF, LiDAR point cloud, maps, motion behavior, etc. are all subscribed directly via DDS with no additional publishing required. To view the URDF (robot model), open a separate terminal and publish robot_description:

source install/local_setup.bash
ros2 run robot_state_publisher robot_state_publisher \
    --ros-args -p robot_description:="$(
      sed "s#filename=\"\\./meshes/#filename=\"file://$URDF_DIR/meshes/#g" \
      "$URDF_DIR/x2_ultra.urdf"
    )"  # Replace $URDF_DIR with the actual URDF file path

System Status, Logs, and Communication Visualization

A robot debugging TUI tool. SSH-friendly, no GUI required. A unified menu-driven Textual app that consolidates the most commonly viewed debugging information on-site, without depending on RViz / Foxglove – works in a plain terminal.

Key Features

  • System Status Dashboard (dash)

    Five real-time panels showing the status of the five major subsystems:

    • MC Action: current motion behavior (e.g., STAND_DEFAULT / LOCOMOTION_DEFAULT) and running / transition status

    • System: SystemState business status (e.g., IN_READY / IN_MOVE)

    • Faults / HDS: active diagnostic codes, alert codes, post-proc signal count, and the most recent code

    • PMU / Battery: PMU temperature, Orin voltage / current, battery capacity, error bits

    • Orin: Jetson Orin CPU / GPU / MEM / temperature, published by a separate orin-monitor process

  • Log Aggregation (logs)

    Consolidates multiple log sources into a single TUI page with real-time viewing, historical queries, and filtering:

    • ROS 2 node logs: ~/.ros/log/<latest_session>/ (default source; skip with --no-ros)

    • Other log files: add with --add-path FILE

    • Optional subscription: /rosout (--rosout, real-time mode only)

    • Automatically parses multiple timestamp formats and normalizes time zones

    • Supports queries such as --since 10m, "yesterday 18:00", and absolute timestamps

    • Supports a filter DSL, e.g., level>=WARN node~=slam connect

  • Communication Visualization (comm)

    Displays topics / services in the current ROS graph; hidden topics are filtered by default, consistent with ros2 topic list. Supports on-demand frequency measurement for selected topics with zero subscription overhead at startup and automatic QoS matching to the publisher.

  • Faults / HDS (faults)

    Dedicated screen for the health diagnostics system:

    • Live: real-time display of Diag / Alert / PostProc data

    • History: query historical faults via GetHdsEventHistory, with filtering by time window, state, and diagnostic code, plus pagination

  • Orin Monitor Publisher (orin-monitor)

    An independent ROS 2 node that collects Jetson Orin resource metrics (CPU / GPU / MEM / temperature, etc.) and publishes them as diagnostic_msgs/DiagnosticArray to /orin/stats. Collection is based on psutil + tegrastats; data can be consumed by dash, bag recording, or other nodes.

Unified entry: ros2 run robotop robotop without arguments opens the menu; use arrow keys to select a screen, Enter to enter, q in a sub-screen returns to the menu, q in the menu exits, Ctrl+C force-quits from anywhere. Direct subcommands are also available: robotop dash / robotop logs --since 1h, etc.

Installation

The workspace dependencies already include aimdk_msgs, rclpy, and diagnostic_msgs. Install the runtime Python packages to the user directory:

pip3 install --user typer textual dateparser

Build:

cd ~/aimdk
colcon build --packages-select robotop
source ~/aimdk/install/setup.bash

Note: the dash Orin card requires a separate orin_monitor_node running in the background to publish /orin/stats; otherwise it will show stale:

ros2 run robotop orin_monitor_node

Command Overview

Command

Function

robotop or robotop menu

Top-level menu, unified entry point

robotop dash

System status panel (MC / System / Faults / PMU / Orin)

robotop logs

Aggregate ROS / app logs, real-time or historical

robotop comm

Topic / service listing + on-demand frequency measurement

robotop faults

HDS diagnostics / alerts live table; --history uses GetHdsEventHistory

robotop orin-monitor

Publish /orin/stats (diagnostic_msgs/DiagnosticArray)

ros2 run robotop robotop                          # Enter menu (recommended)
ros2 run robotop robotop <subcmd> [options]       # Go directly to a screen
ros2 run robotop orin_monitor_node                # Standalone node, suitable for systemd

dash – System Status

ros2 run robotop robotop dash

Five cards:

Card

Data Source

Description

MC Action

/aimdk_5Fmsgs/srv/GetMcAction

Current MC mode + status (RUNNING / TRANSITION / IDLE)

System

/aimdk_5Fmsgs/srv/GetSystemState

Business status name (IN_READY / IN_MOVE, etc.)

Faults / HDS

/aima/hds/{diag_code_list,alert_code_list,post_proc/signal}

Active diag / alert count + most recent code

PMU / Battery

/aima/hal/pmu/state

PMU temperature, voltage / current, battery capacity, error bits

Orin

/orin/stats

Requires orin_monitor_node to be running first

Keys: q returns to menu, r refreshes immediately, Ctrl+C force-quits. A card showing [stale] indicates the corresponding data source is not connected.

logs – Log Aggregation

Real-time (default)

ros2 run robotop robotop logs --follow
ros2 run robotop robotop logs -f --rosout                          # Add /rosout subscription
ros2 run robotop robotop logs -f --add-path ~/.aima/log/aima.log   # Add AIMA logs
ros2 run robotop robotop logs -f --no-ros                          # Skip ROS session log source

Default source: the latest .log file in the most recent ROS session (~/.ros/log/<latest_session>/<latest>.log). Other logs such as AIMA and AIMRT must be explicitly added with --add-path.

Historical Queries

ros2 run robotop robotop logs --since 1h
ros2 run robotop robotop logs --since 7d --level WARN
ros2 run robotop robotop logs --since "2026-06-04 14:00" --until "2026-06-04 16:00"
ros2 run robotop robotop logs --since 30m --grep "fall"

--since / --until support: relative (30s / 10m / 1h / 2d), now, natural language ("yesterday 18:00"), and absolute timestamps ("2026-06-04 14:00").

TUI Key Bindings

Key

Action

/

Enter the top filter input field

Esc

Exit the filter field and return to the log area

Enter

Apply the current filter

Ctrl+L

Clear screen

q

Quit

Filter DSL: top input field, space-separated, all terms must match.

Expression

Meaning

error connect

Message contains both “error” and “connect” (case-insensitive)

level=ERROR

Level exactly equals ERROR

level>=WARN

WARN and above (DEBUG < INFO < WARN < ERROR < FATAL)

node~=slam

Source contains “slam”

source~=aima

Same as above (alias for node~=)

faults – HDS Faults

Live (default)

ros2 run robotop robotop faults

Three tabs correspond to: Diag (/aima/hds/diag_code_list), Alert (/aima/hds/alert_code_list), PostProc (/aima/hds/post_proc/signal).

History

ros2 run robotop robotop faults --history --since 1h
ros2 run robotop robotop faults --history --since 1d --state active
ros2 run robotop robotop faults --history --diag-code 0x6300000001
ros2 run robotop robotop faults --history --alert-code battery_low --asc

Parameter

Meaning

--since / --until

Time window, same parsing rules as logs

--state all/active/recovered

State filter

--diag-code N

Filter by diagnostic code (repeatable)

--alert-code STR

Filter by alert string (repeatable)

--limit N

Maximum entries returned (default 200)

--asc

Chronological order (default: newest first)

comm – Communication Visualization

ros2 run robotop robotop comm                    # Same as ros2 topic list by default
ros2 run robotop robotop comm --include-hidden   # Show all, including _* hidden paths

Two tabs: Topics (columns M / topic / type / pub / sub / hz / count) and Services (columns service / type).

Key

Action

m

Toggle frequency measurement: start subscribing + counting / stop and destroy subscription

M

Stop frequency measurement for all topics at once

/

Enter the top filter bar (substring match on name or type)

Esc

Exit filter and return to the table

r

Re-enumerate immediately

q

Quit

orin-monitor – Orin Resource Publisher

ros2 run robotop orin_monitor_node               # 1 Hz, default
ros2 run robotop orin_monitor_node --rate 2      # 2 Hz

Collection is based on psutil + tegrastats. Published to /orin/stats as diagnostic_msgs/msg/DiagnosticArray. Keys include: cpu_pct_avg, cpu_cores, cpu_pct_core_0..N, gpu_pct, mem_used_mb, mem_total_mb, mem_pct, temp_cpu, temp_gpu, temp_thermal, power_total_mw.

Persistent operation (tmux):

tmux new -d -s orin 'source /opt/ros/humble/setup.bash && source ~/aimdk/install/setup.bash && ros2 run robotop orin_monitor_node'
# View output: tmux attach -t orin  (Ctrl-b d to detach without killing the process)
# Stop: tmux kill-session -t orin