Caasi v0.2.0 Isaac ROS: perception · slam · mapping · motion · nitros · pipeline

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:

GroupCatalog domainCommandsDelegates to
isaac-rosisaacrosstatus · list · doctor · launchthe whole Isaac ROS catalog, plus raw ros2 launch <pkg> <file>
perceptionperceptionstatus · camera · pose · detect · segment · inspect/dev/video*, ros2 topic hz|echo, ros2 node info, Isaac ROS launch files
slamslamstatus · launch · test · benchmarkisaac_ros_visual_slam, slam_toolbox, cartographer_ros
mappingmappingstatus · run · inspectisaac_ros_nvblox, slam_toolbox, nav2_map_server, saved map YAML
motionmotionstatus · serve · plan · execute · benchmarkisaac_ros_cumotion, MoveIt 2 planner packages, ros2 action send_goal
nitrosnitrosstatus · doctorisaac_ros_nitros*, CUDA/TensorRT, ROS_DISABLE_NITROS
pipelineinspectthe 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…]

Everything a run verb accepts is the same four options plus pass-through:

ParameterKindTypeDefaultDescription
--backendoption (-b)strsee belowWhich catalog capability to start (caasi config catalog <domain> lists them).
--nameoptionstrcapability keyName of the tracked run.
--dry-runoptionflagoffPrint the command without starting it.
--jsonoptionflagoffPrint the run record instead of the human summary.
trailing argspass-throughAppended verbatim to the ros2 command line (e.g. use_sim_time:=true).

How a capability is chosen

  1. --backend <capability> when you pass it;
  2. otherwise catalog.<domain>.default from your configuration, if that capability is installed;
  3. 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:

CapabilityUpstream package(s)Launch fileCaasi group
nitrosisaac_ros_nitrosnitros
slamisaac_ros_visual_slamvisual_slam.launch.pyslam
mappingisaac_ros_nvbloxnvblox.launch.pymapping
motionisaac_ros_cumotionisaac_ros_cumotion.launch.pymotion
detectionisaac_ros_object_detectionobject_detection.launch.pyperception
segmentationisaac_ros_image_segmentationimage_segmentation.launch.pyperception
depthisaac_ros_depth_estimation, isaac_ros_disparity_filtersperception
apriltagisaac_ros_apriltagapriltag.launch.pyperception
image_pipelineisaac_ros_image_pipelineperception
tensor_rtisaac_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
Two domains, one upstream package

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…]
ParameterKindTypeDefaultDescription
PACKAGEargumentstrrequiredROS package that owns the launch file.
LAUNCH_FILEargumentstrrequiredLaunch file inside that package.
--nameoptionstrlaunch file nameName of the tracked run.
--dry-runoptionflagoffPrint the command without starting it.
--jsonoptionflagoffPrint the run record.
trailing argspass-throughAppended 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

CapabilityProbed viaCaasi verb
camera corepath glob /dev/video*perception camera
detection coreisaac_ros_object_detectionperception detectobject_detection.launch.py
segmentationisaac_ros_image_segmentationperception segmentimage_segmentation.launch.py
depthisaac_ros_depth_estimation, isaac_ros_disparity_filtersreported by status; launch with isaac-ros launch
poseisaac_ros_apriltag, foundationalpose; topics /tf, /apriltag/detectionsperception 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

CapabilityUpstream packageLaunch fileExpected topicsCaasi verb
visual coreisaac_ros_visual_slamvisual_slam.launch.py/tf, /map, /visual_slam/tracking/odometryslam launch
toolbox coreslam_toolboxonline_async_launch.py/tf, /map, /scanslam launch
cartographercartographer_roscartographer.launch.py/tf, /mapslam 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:

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

CapabilityUpstream package / binaryLaunch fileCaasi verb
nvblox coreisaac_ros_nvbloxnvblox.launch.pymapping run
map_saver corenav2_map_server, binary map_saver_clidetected by status; run it with caasi native ros
occupancyslam_toolboxonline_async_launch.pymapping 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

CapabilityProbed viaLaunch fileCaasi verb
cumotion coreisaac_ros_cumotionisaac_ros_cumotion.launch.pymotion serve
curobopip module curobo / nvidia-curobodetected by status/doctor; bring your own script to motion benchmark
moveit coremoveit_core, moveit_ros_planningdetected by status/doctor; goals via motion plan/execute
ompl coremoveit_planners_ompldetected by status/doctor
pilzpilz_industrial_motion_plannerdetected by status/doctor
stompmoveit_planners_stompdetected by status/doctor
chompmoveit_planners_chompdetected 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…]
ParameterKindTypeDefaultDescription
ACTIONargumentstrrequiredAction name, e.g. /compute_ik.
--jsonoptionflagoffWrap the delegated command instead of streaming it.
trailing argspass-throughAppended 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

CapabilityProbed viaWhat it tells you
core coreisaac_ros_nitrosthe transport library itself
typesisaac_ros_nitros_type_interfacesthe Nitros* message definitions pipeline inspect looks for
bridgeisaac_ros_nitros_bridgebridging between plain and NITROS graphs
disableenv ROS_DISABLE_NITROSset 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:

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]
ParameterKindTypeDefaultDescription
NODEargumentstrwhole graphInspect only this node.
--qos / --no-qosoptionflagonAlso show the QoS of each topic.
--jsonoptionflagoffEmit 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:

The plain-type map the plain finding is computed against — every accelerated type lives in isaac_ros_nitros_interfaces/msg/:

Plain typeAccelerated type
sensor_msgs/msg/ImageNitrosImage
sensor_msgs/msg/CompressedImageNitrosCompressedImage
sensor_msgs/msg/CameraInfoNitrosCameraInfo
sensor_msgs/msg/ImuNitrosImu
sensor_msgs/msg/PointCloud2NitrosPointCloud2
sensor_msgs/msg/LaserScanNitrosLaserScan
nav_msgs/msg/OdometryNitrosOdometry
nav_msgs/msg/OccupancyGridNitrosOccupancyGrid
stereo_msgs/msg/DisparityImageNitrosDisparityImage

On a machine with nothing running (this one) it says so and still exits 0inspect 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.

One graph, three readers

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.