Isaac ROS & Accelerated Groups
Seven groups wrap the GPU-accelerated robotics stack — Isaac ROS, NITROS and the perception,
SLAM, mapping and motion tools around them. None of them implements detection, SLAM or motion
planning: each one reads a capability catalog, probes your machine with
subprocesses, pip metadata and the filesystem, and delegates to the upstream ros2
command that already exists — turning every long-lived launch into a
tracked run.
The seven groups and the catalog domains behind them:
| Group | Catalog domain | Commands | Delegates to |
|---|---|---|---|
isaac-ros | isaacros | status · list · doctor · launch | the whole Isaac ROS catalog, plus raw ros2 launch <pkg> <file> |
perception | perception | status · camera · pose · detect · segment · inspect | /dev/video*, ros2 topic hz|echo, ros2 node info, Isaac ROS launch files |
slam | slam | status · launch · test · benchmark | isaac_ros_visual_slam, slam_toolbox, cartographer_ros |
mapping | mapping | status · run · inspect | isaac_ros_nvblox, slam_toolbox, nav2_map_server, saved map YAML |
motion | motion | status · serve · plan · execute · benchmark | isaac_ros_cumotion, MoveIt 2 planner packages, ros2 action send_goal |
nitros | nitros | status · doctor | isaac_ros_nitros*, CUDA/TensorRT, ROS_DISABLE_NITROS |
pipeline | — | inspect | the live graph: ros2 node list, ros2 node info, ros2 topic info -v |
In one line — which command answers which question:
| You want… | Use |
|---|---|
| “what accelerated stack can I use here?” | isaac-ros status · isaac-ros list · <group> status |
| “start the accelerated thing” | slam launch · mapping run · motion serve · perception detect · isaac-ros launch → all become tracked runs |
| “is it actually working?” (scriptable) | slam test · isaac-ros doctor · nitros doctor · caasi doctor --component accelerated |
| “where does my graph lose the GPU?” | pipeline inspect · perception inspect · nitros doctor |
| “upstream renamed a package” | caasi config set catalog.<domain>.<capability>.<field> […] — no code change |
caasi isaac-ros
The umbrella group: it lists the entire Isaac ROS catalog, tells you which parts are installed and which are running, checks the GPU prerequisites, and launches any package/launch-file pair you name as a tracked run.
The shape every group shares
Six of the seven groups are generated from the catalog by one factory; which commands a group
has is itself catalog data — the domain's verbs:
caasi <group> status [--json]
caasi <group> list [--json] # only where the domain declares "list"
caasi <group> doctor [--verbose] [--json] # only where the domain declares "doctor"
caasi <group> launch|run|serve [--backend|-b CAP] [--name NAME] [--dry-run] [--json] [-- ARGS…]
statusis always generated.listexists only forisaac-ros— it is the catalog browser.doctorexists only forisaac-rosandnitros.- the run verb is the first of
launch/run/servethe domain declares:slam launch,mapping run,motion serve.isaac-ros launchis the exception — it takes a package and a launch file instead of a--backend.perceptionandnitrosdeclare no run verb at all.
Everything a run verb accepts is the same four options plus pass-through:
| Parameter | Kind | Type | Default | Description |
|---|---|---|---|---|
--backend | option (-b) | str | see below | Which catalog capability to start (caasi config catalog <domain> lists them). |
--name | option | str | capability key | Name of the tracked run. |
--dry-run | option | flag | off | Print the command without starting it. |
--json | option | flag | off | Print the run record instead of the human summary. |
| trailing args | pass-through | Appended verbatim to the ros2 command line (e.g. use_sim_time:=true). |
How a capability is chosen
--backend <capability>when you pass it;- otherwise
catalog.<domain>.defaultfrom your configuration, if that capability is installed; - otherwise the first installed capability, in catalog order.
Each of the three ways can fail honestly, and each failure names the config key that would fix it:
shellcaasi slam launch -b nope
Error: Unknown slam capability 'nope'. Known: visual, toolbox, cartographer
caasi slam launch -b visual --dry-run
Error: 'visual' is not available (isaac_ros_visual_slam). Install it, or point Caasi at the renamed upstream with `caasi config set catalog.slam.visual.packages [...]`.
caasi slam launch --dry-run
Error: No slam capability is installed. Install it, or point Caasi at the renamed upstream with `caasi config set catalog.slam.<capability>.<field> [...]`.
# all three exit 1 — the same three messages appear for `mapping run` and `motion serve`
How a capability is detected
One probe chain, first hit wins, and the hit is reported as how /
value: ROS package (ros2 pkg prefix) →
binary (on PATH) → module (pip metadata) →
env variable → filesystem path glob. Caasi never imports the
ecosystem it detects.
Everything launched becomes a tracked run
A run verb starts the resolved command detached and logged, exactly like
caasi run: the record carries backend: ros
(script for benchmark scripts), a kind of isaacros,
slam, mapping, motion, perception or
benchmark, and the extra fields domain and capability —
see Runs & Logs. If your shell is not sourced, the run gets the
environment produced by sourcing /opt/ros/<distro>/setup.bash; if it is
already sourced, nothing is added.
shellcaasi slam launch -b visual use_sim_time:=true
Started run 20260907-101502-visual.
Follow it with: caasi logs 20260907-101502-visual -f
caasi slam launch -b visual --dry-run
Dry run — nothing was launched:
command: /opt/ros/jazzy/bin/ros2 launch isaac_ros_visual_slam visual_slam.launch.py use_sim_time:=true
The catalog, as data
isaac-ros list is the ten-row isaacros domain; the
Group column says which Caasi group wraps each capability. This is the table the
rest of the page expands:
| Capability | Upstream package(s) | Launch file | Caasi group |
|---|---|---|---|
nitros | isaac_ros_nitros | — | nitros |
slam | isaac_ros_visual_slam | visual_slam.launch.py | slam |
mapping | isaac_ros_nvblox | nvblox.launch.py | mapping |
motion | isaac_ros_cumotion | isaac_ros_cumotion.launch.py | motion |
detection | isaac_ros_object_detection | object_detection.launch.py | perception |
segmentation | isaac_ros_image_segmentation | image_segmentation.launch.py | perception |
depth | isaac_ros_depth_estimation, isaac_ros_disparity_filters | — | perception |
apriltag | isaac_ros_apriltag | apriltag.launch.py | perception |
image_pipeline | isaac_ros_image_pipeline | — | perception |
tensor_rt | isaac_ros_tensor_rt | — | — |
The first four rows are marked core in the catalog: status reports
them as missing (and doctor as a failure) when they are absent, while
optional capabilities are reported as absent (a skip).
The version-churn firewall
Isaac ROS renames packages between releases (isaac_ros_visual_slam →
…_v4, split inference packages, moved launch files). Caasi absorbs that in config,
never in code: every field of every capability is overridable under
catalog:, and the override rule is deliberately the only one —
catalog.<domain>.<capability>.<field> replaces that field
wholesale (lists are not appended to). An override may also add a capability, or a whole domain,
that the built-ins do not know about.
caasi config set catalog.<domain>.<capability>.<field> <value>
caasi config catalog [DOMAIN] [--json]
Overridable fields: packages, binaries, modules,
env, paths, topics (lists), launch (a
[package, file] pair), script, label, group,
tool, core. default is reserved: it is the domain setting
that picks the capability when you omit --backend.
shell — upstream renamed visual SLAM to v4caasi config set catalog.slam.visual.packages "[isaac_ros_visual_slam_v4]"
Set catalog.slam.visual.packages = ['isaac_ros_visual_slam_v4'] (saved to /home/you/.config/caasi/config.yaml)
caasi config set catalog.slam.visual.launch "[isaac_ros_visual_slam_v4, visual_slam.launch.py]"
Set catalog.slam.visual.launch = ['isaac_ros_visual_slam_v4', 'visual_slam.launch.py'] (saved to /home/you/.config/caasi/config.yaml)
caasi config set catalog.slam.default visual
Set catalog.slam.default = visual (saved to /home/you/.config/caasi/config.yaml)
caasi slam launch --dry-run
Dry run — nothing was launched:
command: /opt/ros/jazzy/bin/ros2 launch isaac_ros_visual_slam_v4 visual_slam.launch.py
caasi config catalog prints the effective catalog — built-ins merged
with your overrides — so you can see what Caasi will actually probe:
shellcaasi config catalog slam
SLAM slam · ros
capability upstream targets launch file caasi group
visual isaac_ros_visual_slam isaac_ros_visual_slam —
visual_slam.launch.py
toolbox slam_toolbox slam_toolbox —
online_async_launch.py
cartographer cartographer_ros cartographer_ros —
cartographer.launch.py
Every field is overridable: `caasi config set catalog.<domain>.<capability>.<field> <value>`
caasi config catalog nope
Error: Unknown catalog domain: nope. Known domains: isaacros, perception, slam, mapping, motion, nitros, physics, warp, groot, cosmos, usd, data, teleop, sdg
The isaacros domain and the functional domains overlap on purpose:
catalog.isaacros.detection and catalog.perception.detection both
name isaac_ros_object_detection. caasi perception status probes the
perception domain, while caasi perception detect launches the
isaacros capability — so a rename that affects launching is fixed with
caasi config set catalog.isaacros.detection.packages […].
caasi isaac-ros status
caasi isaac-ros status [--json]
One screen for the whole catalog: the workspace (ISAAC_ROS_WS → the
tools.isaacros registry → ~/workspaces/isaac_ros →
~/isaac_ros), the distro, and one row per capability. Never fails — “not installed”
is data.
shell — a machine with Isaac ROS built from sourcecaasi isaac-ros status
Isaac ROS
ROS distro jazzy
Sourced yes
Workspace /home/you/workspaces/isaac_ros
Capability State Detail
nitros installed /home/you/workspaces/isaac_ros/install/isaac_ros_nitros
slam running /home/you/workspaces/isaac_ros/install/isaac_ros_visual_slam
mapping installed /home/you/workspaces/isaac_ros/install/isaac_ros_nvblox
motion installed /home/you/workspaces/isaac_ros/install/isaac_ros_cumotion
detection installed /home/you/workspaces/isaac_ros/install/isaac_ros_object_detection
segmentation absent not installed
depth absent not installed
apriltag installed /home/you/workspaces/isaac_ros/install/isaac_ros_apriltag
image_pipeline absent not installed
tensor_rt installed /home/you/workspaces/isaac_ros/install/isaac_ros_tensor_rt
6 of 10 capabilities installed.
The State column is computed, in this order: running (live nodes
belong to the capability) · installed (a probe hit) · missing (a
core capability with no hit) · absent (an optional capability with no
hit). Detail is the probe value — install prefix, binary path, version, environment
value or matched path — or not installed. The last line is
“N of M capabilities installed.”.
On a machine with plain ROS 2 and no Isaac ROS, the same command is honest about it:
shell — ROS 2 jazzy, no Isaac ROS packagescaasi isaac-ros status
Isaac ROS
ROS distro jazzy
Sourced yes
Capability State Detail
nitros missing not installed
slam missing not installed
mapping missing not installed
motion missing not installed
detection absent not installed
segmentation absent not installed
depth absent not installed
apriltag absent not installed
image_pipeline absent not installed
tensor_rt absent not installed
0 of 10 capabilities installed.
JSON payload — the adapter's keys, then the group's own:
shellcaasi isaac-ros status --json
{
"domain": "isaacros",
"kind": "ros",
"tool": null,
"root": null,
"total": 10,
"installed": 0,
"capabilities": [
{
"key": "slam",
"label": "catalog.isaacros.slam",
"group": "slam",
"core": true,
"found": false,
"how": null,
"value": null,
"launch": ["isaac_ros_visual_slam", "visual_slam.launch.py"],
"script": null,
"nodes": []
}
],
… one object per capability, in catalog order …
"ros_distro": "jazzy",
"ros_root": "/opt/ros/jazzy",
"ros_sourced": true,
"running": [],
"workspace": null
}
running lists the capability keys that have live nodes; each capability object
also carries those nodes. how is
"package"/"binary"/"module"/"env"/"path"
(or null), label is the message key Caasi renders for the capability,
and root/tool/launcher appear for domains tied to a tool
(perception), while ros_distro/ros_root/ros_sourced
appear for ROS domains.
caasi isaac-ros list
caasi isaac-ros list [--json]
The catalog itself, probed: which capability is installed, where, and which group wraps it.
shellcaasi isaac-ros list
Capability Packages Installed Prefix Group
nitros isaac_ros_nitros ✓ /home/you/workspaces/isaac_ros/install/isaac_ros… nitros
slam isaac_ros_visual_slam ✓ /home/you/workspaces/isaac_ros/install/isaac_ros… slam
mapping isaac_ros_nvblox ✓ /home/you/workspaces/isaac_ros/install/isaac_ros… mapping
motion isaac_ros_cumotion ✓ /home/you/workspaces/isaac_ros/install/isaac_ros… motion
detection isaac_ros_object_detection ✓ /home/you/workspaces/isaac_ros/install/isaac_ros… perception
…
Override any field: caasi config set catalog.isaacros.<capability>.<field> <value>
caasi isaac-ros list --json | jq '.capabilities[1]'
{
"capability": "slam",
"packages": ["isaac_ros_visual_slam"],
"installed": true,
"prefix": "/home/you/workspaces/isaac_ros/install/isaac_ros_visual_slam",
"group": "slam",
"launch": ["isaac_ros_visual_slam", "visual_slam.launch.py"]
}
Installed is ✓ or •; Prefix and
Group print — when empty. JSON is
{"domain", "capabilities": [{"capability", "packages", "installed", "prefix", "group", "launch"}]}.
Always exits 0.
caasi isaac-ros doctor
caasi isaac-ros doctor [--verbose] [--json]
caasi doctor --component accelerated
The group's doctor runs four check sections — ros,
accelerated, nvidia, graphics — and exits 1
if any check failed. Hints are printed for failures and warnings; --verbose also
shows hints for passing checks.
The accelerated section is the one worth knowing: it walks the
isaacros, nitros and motion domains (de-duplicated by
upstream target, since isaacros lists the same packages), then adds the NITROS
prerequisites — CUDA, TensorRT, RMW_IMPLEMENTATION, ROS_DOMAIN_ID. The
same section is available from the global doctor:
shellcaasi doctor --component accelerated
Environment Diagnostics
GPU-Accelerated Robotics
✗ NITROS transport — not found (isaac_ros_nitros)
↳ Install it, or point Caasi at the renamed upstream with `caasi config set catalog.isaacros.nitros.packages [...]`.
✗ Visual SLAM — not found (isaac_ros_visual_slam)
↳ Install it, or point Caasi at the renamed upstream with `caasi config set catalog.isaacros.slam.packages [...]`.
✗ nvblox mapping — not found (isaac_ros_nvblox)
✗ cuMotion — not found (isaac_ros_cumotion)
• Object detection — not found (isaac_ros_object_detection)
• Image segmentation — not found (isaac_ros_image_segmentation)
• Depth estimation — not found (isaac_ros_depth_estimation, isaac_ros_disparity_filters)
• AprilTag detection — not found (isaac_ros_apriltag)
• Image pipeline — not found (isaac_ros_image_pipeline)
• TensorRT inference — not found (isaac_ros_tensor_rt)
• NITROS type interfaces — not found (isaac_ros_nitros_type_interfaces)
• NITROS bridge — not found (isaac_ros_nitros_bridge)
• ROS_DISABLE_NITROS — not found (ROS_DISABLE_NITROS)
• cuRobo — not found (curobo, nvidia-curobo)
✗ MoveIt 2 — not found (moveit_core, moveit_ros_planning)
✗ OMPL — not found (moveit_planners_ompl)
• Pilz — not found (pilz_industrial_motion_planner)
• STOMP — not found (moveit_planners_stomp)
• CHOMP — not found (moveit_planners_chomp)
✓ CUDA (driver) — 13.0
! TensorRT (python) — no tensorrt distribution found
↳ Isaac ROS inference nodes need TensorRT; install it or use the Isaac ROS container.
• RMW_IMPLEMENTATION — not set (ROS 2 default)
• ROS_DOMAIN_ID — not set (domain 0)
✘ 6 issue(s) found, 1 warning(s). Run with --verbose for hints.
exit code: 1
When the ros2 CLI itself is missing, the whole section degrades to a single skip
row: • Isaac ROS — skipped (ROS 2 CLI unavailable) with the hint
Install ROS 2 to enable Nav2 / MoveIt 2 / ros2_control checks.
JSON mode is the same list of checks plus an explicit exit code:
shellcaasi isaac-ros doctor --json | jq '.sections, .checks[0], .exit_code'
["ros", "accelerated", "nvidia", "graphics"]
{
"section": "accelerated",
"name": "NITROS transport",
"status": "fail",
"detail": "not found (isaac_ros_nitros)",
"hint": "Install it, or point Caasi at the renamed upstream with `caasi config set catalog.isaacros.nitros.packages [...]`."
}
1
Group doctor JSON adds domain and sections to
{"checks": [{"section", "name", "status", "detail", "hint"}], "exit_code"};
status is one of ok, warn, fail,
skip (rendered ✓ ! ✗ •).
caasi isaac-ros launch
caasi isaac-ros launch PACKAGE LAUNCH_FILE [--name NAME] [--dry-run] [--json] [-- LAUNCH_ARGS…]
| Parameter | Kind | Type | Default | Description |
|---|---|---|---|---|
PACKAGE | argument | str | required | ROS package that owns the launch file. |
LAUNCH_FILE | argument | str | required | Launch file inside that package. |
--name | option | str | launch file name | Name of the tracked run. |
--dry-run | option | flag | off | Print the command without starting it. |
--json | option | flag | off | Print the run record. |
| trailing args | pass-through | Appended verbatim as launch arguments. |
This is the escape hatch for anything the catalog does not name: the two arguments go straight
into ros2 launch, so no install check happens up front — if the package is missing,
the run fails and caasi logs shows the ros2 error. The record carries
kind: isaacros with package and launch as extra fields.
shellcaasi isaac-ros launch isaac_ros_visual_slam visual_slam.launch.py --dry-run
Dry run — nothing was launched:
command: /opt/ros/jazzy/bin/ros2 launch isaac_ros_visual_slam visual_slam.launch.py
caasi isaac-ros launch isaac_ros_nvblox nvblox.launch.py --name nvblox use_sim_time:=true
Started run 20260907-102214-nvblox.
Follow it with: caasi logs 20260907-102214-nvblox -f
caasi run stop nvblox
caasi perception
Cameras from /dev, image topics and rates from ros2, GPU detection
and segmentation from the Isaac ROS launch files, node interfaces from ros2 node info.
The domain is mixed and tied to the isaacsim tool, so its
status reports a launcher instead of distro rows.
Capabilities
| Capability | Probed via | Caasi verb |
|---|---|---|
camera core | path glob /dev/video* | perception camera |
detection core | isaac_ros_object_detection | perception detect → object_detection.launch.py |
segmentation | isaac_ros_image_segmentation | perception segment → image_segmentation.launch.py |
depth | isaac_ros_depth_estimation, isaac_ros_disparity_filters | reported by status; launch with isaac-ros launch |
pose | isaac_ros_apriltag, foundationalpose; topics /tf, /apriltag/detections | perception pose (echoes /apriltag/detections by default) |
caasi perception status
caasi perception status [--json]
shell — Isaac Sim registered, no Isaac ROScaasi perception status
Perception
Root /opt/isaac-sim-6.0
Launcher /opt/isaac-sim-6.0/python.sh
Capability State Detail
camera installed /dev/video0
detection missing not installed
segmentation absent not installed
depth absent not installed
pose absent not installed
1 of 5 capabilities installed.
caasi perception status --json | jq '{kind, tool, launcher, running}'
{ "kind": "mixed", "tool": "isaacsim",
"launcher": "/opt/isaac-sim-6.0/python.sh", "running": [] }
The camera capability is a path probe, so it is “installed” as soon as a video
device exists — it says nothing about Isaac ROS.
caasi perception camera
caasi perception camera [--hz TOPIC] [--duration 5.0] [--window 10] [--json] [-- HZ_ARGS…]
Two modes in one command. Without --hz it lists video devices
(/dev/video* plus /sys/class/video4linux/*/name) and the live topics
carrying sensor_msgs/msg/Image or sensor_msgs/msg/CompressedImage:
shellcaasi perception camera
Video devices
Device Name
/dev/video0 HIK 2K Camera: HIK 2K Camera
/dev/video1 HIK 2K Camera: HIK 2K Camera
No live image topics (is a camera or simulator publishing?).
caasi perception camera --json
{
"devices": [{"device": "/dev/video0", "name": "HIK 2K Camera: HIK 2K Camera"}],
"topics": []
}
With a running graph the second table appears:
shellcaasi perception camera
Video devices
Device Name
/dev/video0 HIK 2K Camera: HIK 2K Camera
Live image topics
Topic Type
/camera/image_raw sensor_msgs/msg/Image
/left/image_rect sensor_msgs/msg/Image
With --hz TOPIC it becomes a bounded rate sample: Caasi runs
ros2 topic hz --window <N> <topic> in the foreground, wrapped in
coreutils timeout <duration>, and passes the output (and exit code) through.
--json returns the parsed sample instead:
shellcaasi perception camera --hz /camera/image_raw --duration 5
average rate: 29.998
min: 0.032s max: 0.035s std dev: 0.00086s window: 10
caasi perception camera --hz /camera/image_raw --json
{ "topic": "/camera/image_raw", "rate": 29.998, "detail": "29.998 Hz" }
caasi perception camera --hz /nope --json
{ "topic": "/nope", "rate": null, "detail": "no messages received" }
If coreutils timeout is unavailable the sample is unbounded and Caasi says so:
coreutils `timeout` not found — the sample is not time-bounded. With no devices and
no topics at all you also get No camera device and no image topic found. Exit code
is 0 in list mode; in --hz mode it mirrors ros2.
caasi perception pose
caasi perception pose [--topic|-t NAME] [--json] [-- ECHO_ARGS…]
A foreground ros2 topic echo of the pose topic, with the exit code mirrored. The
default topic comes from the catalog — the last entry of the pose capability's
topics, i.e. /apriltag/detections:
shellcaasi perception pose --topic /tf --once
---
transforms:
- header:
stamp: {sec: 1757203201, nanosec: 118}
frame_id: camera_00_link
caasi perception pose --json | jq '{command, returncode}'
{ "command": ["/opt/ros/jazzy/bin/ros2", "topic", "echo", "/apriltag/detections"],
"returncode": 0 }
JSON mode wraps the delegated command:
{"command", "returncode", "stdout", "stderr"} — the convention shared with
caasi native. Streaming without a bound would block forever, so pass
your own ros2 flags (--once, --no-arr) as pass-through
args.
caasi perception detect / segment
caasi perception detect [--name NAME] [--dry-run] [--json] [-- LAUNCH_ARGS…]
caasi perception segment [--name NAME] [--dry-run] [--json] [-- LAUNCH_ARGS…]
Both resolve the corresponding isaacros capability (detection,
segmentation) and start its launch file as a tracked run with
kind: perception:
shellcaasi perception detect --dry-run
Dry run — nothing was launched:
command: /opt/ros/jazzy/bin/ros2 launch isaac_ros_object_detection object_detection.launch.py
caasi perception detect --name detect-people
Started run 20260907-103011-detect-people.
Follow it with: caasi logs 20260907-103011-detect-people -f
caasi perception segment
Error: 'segmentation' is not installed. See what is available with: caasi isaac-ros list
exit code: 1
That last message is what you get on a machine without Isaac ROS — detect fails
the same way with 'detection'. Both accept pass-through launch arguments, so
model paths and topic remaps go straight to the upstream launch file.
caasi perception inspect
caasi perception inspect [NODE] [--qos/--no-qos] [--json]
ros2 node info for every running perception node — or for the one you name. A
node belongs to the group when its name contains one of the catalog targets (an
isaac_ros_ prefix is also matched stripped, so /visual_slam counts).
With --qos (the default) each subscriber/publisher topic is also expanded with
ros2 topic info -v:
shellcaasi perception inspect
No perception nodes are running.
Start one (caasi perception detect) or pass a node name.
caasi perception inspect /isaac_ros_detection
/isaac_ros_detection
Subscribers
/camera/image_raw sensor_msgs/msg/Image
publisher /camera_node: RELIABLE / VOLATILE
subscription /isaac_ros_detection: BEST_EFFORT / VOLATILE
Publishers
/detections_output isaac_ros_nitros_interfaces/msg/NitrosImage
publisher /isaac_ros_detection: RELIABLE / VOLATILE
caasi perception inspect /isaac_ros_detection --json | jq '.nodes[0].interfaces.Publishers'
[{"endpoint": "/detections_output", "type": "isaac_ros_nitros_interfaces/msg/NitrosImage"}]
QoS rows read <endpoint> <node>: <reliability> / <durability>.
JSON is {"nodes": [{"node", "interfaces": {<section>: [{"endpoint", "type"}]}}]}
with the sections Subscribers, Publishers, Service Servers,
Service Clients, Action Servers, Action Clients; with
nothing running it is {"nodes": []} and the exit code stays 0.
caasi slam
Pick a SLAM backend from the catalog, launch it as a tracked run, then prove it works: node alive, expected topics published, rates sampled.
Capabilities
| Capability | Upstream package | Launch file | Expected topics | Caasi verb |
|---|---|---|---|---|
visual core | isaac_ros_visual_slam | visual_slam.launch.py | /tf, /map, /visual_slam/tracking/odometry | slam launch |
toolbox core | slam_toolbox | online_async_launch.py | /tf, /map, /scan | slam launch |
cartographer | cartographer_ros | cartographer.launch.py | /tf, /map | slam launch |
Pin the CPU backend as your default once, and every slam command follows:
shellcaasi config set catalog.slam.default toolbox
Set catalog.slam.default = toolbox (saved to /home/you/.config/caasi/config.yaml)
caasi slam status
SLAM
ROS distro jazzy
Sourced yes
Capability State Detail
visual absent not installed
toolbox running /opt/ros/jazzy
cartographer absent not installed
1 of 3 capabilities installed.
caasi slam launch
caasi slam launch [--backend|-b CAP] [--name NAME] [--dry-run] [--json] [-- LAUNCH_ARGS…]
Starts the resolved backend's launch file as a tracked run (kind: slam) named
after the capability unless you pass --name:
shellcaasi slam launch --dry-run
Dry run — nothing was launched:
command: /opt/ros/jazzy/bin/ros2 launch slam_toolbox online_async_launch.py
caasi slam launch -b visual use_sim_time:=true
Started run 20260907-104122-visual.
Follow it with: caasi logs 20260907-104122-visual -f
caasi slam launch -b nope
Error: Unknown slam capability 'nope'. Known: visual, toolbox, cartographer
caasi slam test
caasi slam test [--backend|-b CAP] [--hz] [--duration 5.0] [--window 10] [--verbose] [--json]
The readiness gate, as a ladder: backend installed → its node alive in the graph → each
expected topic published → (with --hz) each topic's rate sampled. Exit
0 when nothing failed, 1 when any check did:
shellcaasi slam test
✓ SLAM Toolbox — /opt/ros/jazzy
✓ SLAM node — /slam_toolbox
✓ Topic /tf — /tf
✓ Topic /map — /map
✗ Topic /scan — not published
↳ Check the graph with: caasi ros graph (expected /scan)
exit code: 1
caasi slam test --hz --json | jq '{backend, exit_code, checks: [.checks[].name]}'
{ "backend": "toolbox", "exit_code": 0,
"checks": ["SLAM Toolbox", "SLAM node", "Topic /tf", "Topic /map", "Topic /scan",
"Rate /tf", "Rate /map", "Rate /scan"] }
Rate rows report <rate> Hz (ok) or
no messages received (warn — a warning never fails the gate). With no
node running you get ✗ SLAM node — no SLAM node is running with the hint
Start one with: caasi slam launch, and every topic row becomes a skip. With no
ros2 CLI at all the whole test is one skip row. JSON is
{"domain": "slam", "backend", "checks", "exit_code"}.
shell — nothing installedcaasi slam test
Error: No slam capability is installed. Install it, or point Caasi at the renamed upstream with `caasi config set catalog.slam.<capability>.<field> [...]`.
exit code: 1
caasi slam benchmark
caasi slam benchmark [--script PATH] [--topic|-t NAME] [--backend|-b CAP] [--duration 10.0]
[--window 10] [--name NAME] [--dry-run] [--json] [-- SCRIPT_ARGS…]
Two flavours, both tracked runs with kind: benchmark:
- topic rate (default):
ros2 topic hz --window <N> <topic>bounded bytimeout <duration>, namedslam-hz-<topic>. The topic defaults to the first expected topic of the resolved backend. - your script (
--script): run through the run system withbackend: script— a.pyfile uses the CLI's own interpreter, anything else is executed directly.
shellcaasi slam benchmark --script bench/ate.py --dry-run
Dry run — nothing was launched:
command: /home/you/.caasi/venv/bin/python3 bench/ate.py
caasi slam benchmark --topic /visual_slam/tracking/odometry --duration 30
Started run 20260907-105533-slam-hz-visual-slam-tracking-odometry.
Follow it with: caasi logs 20260907-105533-slam-hz-visual-slam-tracking-odometry -f
When finished: caasi benchmark report 20260907-105533-slam-hz-visual-slam-tracking-odometry
caasi slam benchmark --script bench/nope.py
Error: Script not found: bench/nope.py
Without a resolvable backend and without --script/--topic there is
nothing to sample: Error: No topic to sample; pass --topic or a --script. When
coreutils timeout is missing the run is started unbounded and the dry run says
coreutils `timeout` not found — the sample is not time-bounded.
caasi mapping
Run a mapper as a tracked run, then read the map it saved — offline, from the YAML and the
header bytes of its image. No image library and no ROS needed for inspect.
Capabilities
| Capability | Upstream package / binary | Launch file | Caasi verb |
|---|---|---|---|
nvblox core | isaac_ros_nvblox | nvblox.launch.py | mapping run |
map_saver core | nav2_map_server, binary map_saver_cli | — | detected by status; run it with caasi native ros |
occupancy | slam_toolbox | online_async_launch.py | mapping run -b occupancy |
caasi mapping run
caasi mapping run [--backend|-b CAP] [--name NAME] [--dry-run] [--json] [-- LAUNCH_ARGS…]
shellcaasi mapping run --dry-run
Dry run — nothing was launched:
command: /opt/ros/jazzy/bin/ros2 launch isaac_ros_nvblox nvblox.launch.py
caasi mapping run -b occupancy --name hall-map use_sim_time:=true
Started run 20260907-110412-hall-map.
Follow it with: caasi logs 20260907-110412-hall-map -f
caasi mapping run -b map_saver
Error: No launch file is defined for 'map_saver' in domain 'mapping'.
caasi mapping run -b occupancy
Error: 'occupancy' is not available (slam_toolbox). Install it, or point Caasi at the renamed upstream with `caasi config set catalog.mapping.occupancy.packages [...]`.
map_saver is a detection-only capability: it has a package and a binary but no
launch file, so the run verb refuses it honestly instead of inventing a command line. Save a map
with caasi native ros run nav2_map_server map_saver_cli -f maps/hall.
caasi mapping inspect
caasi mapping inspect MAP_YAML [--json]
Reads a Nav2/ROS map YAML — image, resolution, origin,
occupied_thresh, free_thresh, mode — and the size of the
referenced image, taken from the PNG IHDR or PGM/PBM header bytes. It also computes
the real-world scale (pixels × resolution):
maps/hall.yamlimage: hall.pgm
mode: trinary
resolution: 0.05
origin: [-10.0, -10.0, 0.0]
occupied_thresh: 0.65
free_thresh: 0.25
shellcaasi mapping inspect maps/hall.yaml
hall.yaml
Image maps/hall.pgm
Exists yes
Size 800 × 600
Resolution 0.05
Origin [-10.0, -10.0, 0.0]
Occupied 0.65
Free 0.25
Mode trinary
Scale 40.0 m × 30.0 m
caasi mapping inspect maps/hall.yaml --json
{
"path": "maps/hall.yaml",
"image": "maps/hall.pgm",
"image_exists": true,
"width": 800,
"height": 600,
"resolution": 0.05,
"origin": [-10.0, -10.0, 0.0],
"occupied_thresh": 0.65,
"free_thresh": 0.25,
"mode": "trinary",
"width_metres": 40.0,
"height_metres": 30.0
}
Exit 0 when the image exists, 1 when it does not — in both human and
JSON mode (a missing image leaves width/height/width_metres/
height_metres as null and prints
Error: The map image is missing: maps/hall.pgm). Other failures:
Error: Map file not found: <path> and
Error: Not a readable map YAML: <error>.
shellcaasi mapping inspect maps/broken.yaml
broken.yaml
Image maps/missing.pgm
Exists no
Size —
Resolution 0.05
Origin [-10.0, -10.0, 0.0]
Occupied 0.65
Free 0.25
Mode trinary
Scale —
Error: The map image is missing: maps/missing.pgm
exit code: 1
caasi motion
GPU motion planning servers and the goals you send them. status adds a
GPU row (from nvidia-smi) because every capability here wants one;
planning itself always happens inside cuMotion, cuRobo or MoveIt 2.
Capabilities
| Capability | Probed via | Launch file | Caasi verb |
|---|---|---|---|
cumotion core | isaac_ros_cumotion | isaac_ros_cumotion.launch.py | motion serve |
curobo | pip module curobo / nvidia-curobo | — | detected by status/doctor; bring your own script to motion benchmark |
moveit core | moveit_core, moveit_ros_planning | — | detected by status/doctor; goals via motion plan/execute |
ompl core | moveit_planners_ompl | — | detected by status/doctor |
pilz | pilz_industrial_motion_planner | — | detected by status/doctor |
stomp | moveit_planners_stomp | — | detected by status/doctor |
chomp | moveit_planners_chomp | — | detected by status/doctor |
cumotion is the only capability with a launch file, so it is the only one
motion serve can start. The MoveIt family are planner libraries: Caasi
detects them so status and caasi doctor --component accelerated can
report your toolchain, but it never launches a MoveIt server itself — that stays your own
MoveIt/Nav2 launch. What Caasi does talk to is whatever action server is already running.
caasi motion status
caasi motion status [--json]
The GPU row comes from nvidia-smi (the JSON payload carries it as
"gpu", null when there is no NVIDIA GPU):
shell — this machine: no motion stackcaasi motion status
Motion Planning
ROS distro jazzy
Sourced yes
GPU NVIDIA GeForce RTX 3080
Capability State Detail
cumotion missing not installed
curobo absent not installed
moveit missing not installed
ompl missing not installed
pilz absent not installed
stomp absent not installed
chomp absent not installed
0 of 7 capabilities installed.
caasi motion status --json | jq '{domain, kind, total, installed, gpu}'
{ "domain": "motion", "kind": "ros", "total": 7, "installed": 0,
"gpu": "NVIDIA GeForce RTX 3080" }
cumotion, moveit and ompl are core — not finding them
is a red missing; the rest are optional and stay grey absent.
caasi motion serve
caasi motion serve [--backend|-b CAP] [--name NAME] [--dry-run] [--json] [-- LAUNCH_ARGS…]
The domain's run verb: a tracked run with kind: motion, backend: ros
and the extra fields domain/capability, exactly like
slam launch and mapping run.
shell — cuMotion installedcaasi motion serve --dry-run
Dry run — nothing was launched:
command: /opt/ros/jazzy/bin/ros2 launch isaac_ros_cumotion isaac_ros_cumotion.launch.py
caasi motion serve --name planner use_sim_time:=true
Started run 20260907-112043-planner.
Follow it with: caasi logs 20260907-112043-planner -f
caasi motion serve -b curobo
Error: No launch file is defined for 'curobo' in domain 'motion'.
Every other capability fails that same honest way — a pip module or a planner library has no
launch file, and Caasi refuses to invent one. On a machine with nothing installed (this one),
even cumotion stops at the probe:
shell — nothing installedcaasi motion serve -b cumotion --dry-run
Error: 'cumotion' is not available (isaac_ros_cumotion). Install it, or point Caasi at the renamed upstream with `caasi config set catalog.motion.cumotion.packages [...]`.
exit code: 1
caasi motion plan / execute
caasi motion plan ACTION [--json] [-- GOAL_ARGS…]
caasi motion execute ACTION [--json] [-- GOAL_ARGS…]
| Parameter | Kind | Type | Default | Description |
|---|---|---|---|---|
ACTION | argument | str | required | Action name, e.g. /compute_ik. |
--json | option | flag | off | Wrap the delegated command instead of streaming it. |
| trailing args | pass-through | Appended verbatim to ros2 action send_goal (goal type, YAML payload, --feedback). |
Foreground pass-throughs, not tracked runs. Both first read ros2 action list and
fail before sending anything when there is no server — or no server by that name:
shellcaasi motion plan /compute_ik
Error: No action server is reachable. Start one with: caasi motion serve
exit code: 1
caasi motion plan /nope
Error: No action server for '/nope'. Available: /compute_ik, /plan_path
exit code: 1
caasi motion plan /compute_ik isaac_ros_cumotion/action/ComputeIK "{…}"
# everything after ACTION goes to `ros2 action send_goal` verbatim —
# output and exit code are ros2's own; --json wraps them as
# {"command", "returncode", "stdout", "stderr"}, like perception pose
caasi motion benchmark
caasi motion benchmark SCRIPT [--name NAME] [--dry-run] [--json] [-- SCRIPT_ARGS…]
Unlike slam benchmark (which can sample a topic rate itself), motion benchmarking
is always your script — SCRIPT is a required positional argument. It runs
as a tracked run with kind: benchmark, backend: script, named after the
script's stem unless you pass --name; a .py file uses the CLI's own
interpreter, anything else is executed directly, and trailing args go to the script:
shellcaasi motion benchmark bench/plan.py --dry-run
Dry run — nothing was launched:
command: /home/you/.caasi/venv/bin/python3 bench/plan.py
caasi motion benchmark bench/plan.py --name cumotion-bench
Started run 20260907-114512-cumotion-bench.
Follow it with: caasi logs 20260907-114512-cumotion-bench -f
When finished: caasi benchmark report 20260907-114512-cumotion-bench
caasi motion benchmark bench/nope.py
Error: Script not found: bench/nope.py
caasi nitros
NITROS is the zero-copy GPU transport that the rest of the accelerated stack rides on — CUDA
memory published as ROS 2 messages so GPU nodes share buffers instead of copying. There is
nothing to launch, so the domain's verbs are just status and doctor:
no run verb, no list.
Capabilities
| Capability | Probed via | What it tells you |
|---|---|---|
core core | isaac_ros_nitros | the transport library itself |
types | isaac_ros_nitros_type_interfaces | the Nitros* message definitions pipeline inspect looks for |
bridge | isaac_ros_nitros_bridge | bridging between plain and NITROS graphs |
disable | env ROS_DISABLE_NITROS | set in the environment → Isaac ROS nodes fall back to plain types |
caasi nitros status
caasi nitros status [--json]
The extra rows are CUDA and TensorRT; a row whose probe finds
nothing is omitted from the human view (no TensorRT distribution here, so no TensorRT row), but
the JSON always carries both keys:
shell — CUDA present, NITROS not installedcaasi nitros status
NITROS
ROS distro jazzy
Sourced yes
CUDA 13.0
Capability State Detail
core missing not installed
types absent not installed
bridge absent not installed
disable absent not installed
0 of 4 capabilities installed.
caasi nitros status --json | jq '{cuda, tensorrt}'
{ "cuda": "13.0", "tensorrt": null }
ROS_DISABLE_NITROS is an env probe: when it is set, the disable
capability flips to installed with the variable's value as its detail. Caasi only
reports it — Isaac ROS nodes themselves read it and fall back to plain message types,
which is the escape hatch when a NITROS build misbehaves at runtime:
shellROS_DISABLE_NITROS=1 caasi nitros status
NITROS
ROS distro jazzy
Sourced yes
CUDA 13.0
Capability State Detail
core missing not installed
types absent not installed
bridge absent not installed
disable installed 1
1 of 4 capabilities installed.
ROS_DISABLE_NITROS=1 caasi nitros status --json | jq '.capabilities[3] | {key, found, how, value}'
{ "key": "disable", "found": true, "how": "env", "value": "1" }
With NITROS disabled the running graph is plain by construction — caasi
pipeline inspect shows exactly which topics that affects.
caasi nitros doctor
caasi nitros doctor [--verbose] [--json]
Three check sections — accelerated, nvidia, graphics
(the isaac-ros doctor set minus ros) — plus two NITROS extras:
- What NITROS does — always a skip row; it explains rather than checks, and
its hint (
Nodes must be NITROS-aware; a plain node in the chain forces a CPU copy.) appears only with--verbose. - Graph mixing — the same live-graph findings as
pipeline inspect, as one row: a warning when topics cross a GPU/CPU boundary,okwhen nodes are running and none do, and no row at all when nothing is running.
shell — this machine, empty graphcaasi nitros doctor
# …the same accelerated capability rows as `caasi isaac-ros doctor`, then:
✓ CUDA (driver) — 13.0
! TensorRT (python) — no tensorrt distribution found
↳ Isaac ROS inference nodes need TensorRT; install it or use the Isaac ROS container.
• RMW_IMPLEMENTATION — not set (ROS 2 default)
• ROS_DOMAIN_ID — not set (domain 0)
✓ NVIDIA driver — driver 580.173.02
✓ GPU — NVIDIA GeForce RTX 3080, 10.0 GiB VRAM
✓ CUDA — CUDA 13.0 (driver-reported)
✓ Vulkan — libvulkan available
✓ Display server — display available
• What NITROS does — Publishes CUDA memory as ROS 2 messages so GPU nodes share buffers instead of copying.
exit code: 1
shell — with a graph runningcaasi nitros doctor
! Graph mixing — 1 topic(s) cross a GPU/CPU boundary, e.g. /camera/image_raw
↳ Use the NITROS type on both sides, or accept the copy at that boundary.
caasi nitros doctor --json | jq '.sections, .exit_code'
["accelerated", "nvidia", "graphics"]
1
A clean graph reports ✓ Graph mixing — no GPU/CPU boundary in the running graph
(7 node(s)). Exit is 1 if any check failed — the Graph mixing warning alone
does not fail the doctor.
caasi pipeline
Not a catalog group: there is no domain behind it, nothing to install and nothing to launch.
One command reads the live ROS graph and reports where your pipeline loses the GPU —
the same findings that back the nitros doctor Graph mixing row.
caasi pipeline inspect
caasi pipeline inspect [NODE] [--qos/--no-qos] [--json]
| Parameter | Kind | Type | Default | Description |
|---|---|---|---|---|
NODE | argument | str | whole graph | Inspect only this node. |
--qos / --no-qos | option | flag | on | Also show the QoS of each topic. |
--json | option | flag | off | Emit the findings payload. |
The data path is pure ros2: nodes from ros2 node list (or the one
NODE you name), each node's endpoints from ros2 node info, and — with
--qos — per-topic endpoints from ros2 topic info -v. A node counts as
accelerated when its name contains isaac_ros_ or nitros
(case-insensitive). Everything reported is computed from that; nothing is estimated:
- plain — a topic carrying a plain message type that has a NITROS counterpart, with at least one accelerated node on it. A pure-CPU graph yields nothing: there is no acceleration to lose there.
- boundaries — an accelerated node and a plain node on the same topic: every message across it is a GPU↔CPU copy.
- qos — incompatible pub/sub pairs, with the reason spelled out:
BEST_EFFORT publisher cannot reach a RELIABLE subscriberorVOLATILE publisher cannot reach a TRANSIENT_LOCAL subscriber.
The plain-type map the plain finding is computed against — every accelerated
type lives in isaac_ros_nitros_interfaces/msg/:
| Plain type | Accelerated type |
|---|---|
sensor_msgs/msg/Image | NitrosImage |
sensor_msgs/msg/CompressedImage | NitrosCompressedImage |
sensor_msgs/msg/CameraInfo | NitrosCameraInfo |
sensor_msgs/msg/Imu | NitrosImu |
sensor_msgs/msg/PointCloud2 | NitrosPointCloud2 |
sensor_msgs/msg/LaserScan | NitrosLaserScan |
nav_msgs/msg/Odometry | NitrosOdometry |
nav_msgs/msg/OccupancyGrid | NitrosOccupancyGrid |
stereo_msgs/msg/DisparityImage | NitrosDisparityImage |
On a machine with nothing running (this one) it says so and still exits 0 —
inspect reports, it does not gate:
shell — empty graphcaasi pipeline inspect
No nodes are running.
Start your graph first; this command reads the live ROS graph.
caasi pipeline inspect --json
{
"nodes": [],
"plain": [],
"boundaries": [],
"qos": []
}
The empty-graph payload short-circuits before any finding is computed, so it carries only
those four keys. With nodes running the full payload has six, in order: nodes (the
inspected node names), accelerated (the subset matching the tokens),
topics (count of distinct typed topics seen), plain,
boundaries, qos:
shell — a graph that mixes GPU and CPUcaasi pipeline inspect
5 node(s), 2 accelerated, 6 topic(s) in the graph.
Topics that could be accelerated
Topic Type Accelerated type
/camera/image_raw sensor_msgs/msg/Image isaac_ros_nitros_interfaces/msg/NitrosImage
! /camera/image_raw: GPU/CPU copy between /isaac_ros_detection and /camera_node
! /camera/image_raw: /camera_node → /isaac_ros_detection — BEST_EFFORT publisher cannot reach a RELIABLE subscriber
Publish the accelerated type on both sides of a boundary to avoid the copy.
shell — a clean accelerated graphcaasi pipeline inspect
7 node(s), 4 accelerated, 9 topic(s) in the graph.
No acceleration boundary found in the running graph.
caasi pipeline inspect --json | jq '.plain[0], .boundaries[0], .qos[0]'
{ "topic": "/camera/image_raw", "type": "sensor_msgs/msg/Image",
"accelerated_type": "isaac_ros_nitros_interfaces/msg/NitrosImage",
"nodes": ["/camera_node", "/isaac_ros_detection"] }
{ "topic": "/camera/image_raw", "type": "sensor_msgs/msg/Image",
"accelerated": ["/isaac_ros_detection"], "plain": ["/camera_node"] }
{ "topic": "/camera/image_raw", "publisher": "/camera_node",
"subscriber": "/isaac_ros_detection",
"reason": "BEST_EFFORT publisher cannot reach a RELIABLE subscriber" }
The closing line depends only on plain + boundaries: a graph with
QoS mismatches but no acceleration boundary still ends with No acceleration boundary found
in the running graph. Pass a single NODE to compute findings from just that
node's endpoints. --no-qos skips the ros2 topic info -v round-trips and
leaves qos empty.
pipeline inspect (whole-graph findings), nitros doctor (the same
findings as one Graph mixing row) and perception
inspect (per-node interfaces and QoS) all read the same live graph through
ros2. None of them launches anything — start the graph with the run verbs, then
read it here.