Artefacts ツールキットのナビゲーションヘルパー

Artefacts ツールキットのナビゲーションヘルパーは、自動ナビゲーションテストのための便利な関数と CLI ツールを提供します。RViz 上で直接ルートを記録し、その 2D マップからシミュレータ用のワールドを生成し、テストでルートを読み込んで走行させることができます。

インポート方法:

from artefacts_toolkit.navigation import <function>

ルートの記録

ルートは、記録時の 2D Pose Estimate(initial_pose)、ウェイポイント、および記録に使用したマップを保持する YAML ファイルです。マップの yaml と pgm の両方のファイルが必要です。テストを実行するディレクトリから記録してください:

artefacts-route record [save/to/route.yaml] --map <map/to/load.yaml>

マップを読み込んだ Nav2 の map_server と RViz が起動します。2D Pose Estimate で開始位置を設定し、Publish Point でウェイポイントをクリックしてください。RViz を閉じるか、ターミナルで Enter を押すとルートが routes/<date>_<time>.yaml に保存されます。特定の場所に保存するにはパス([save/to/route.yaml])を渡してください。

オプション 説明
FILE ルートの保存先(英数字、-、_ が使用可能)。デフォルト:./routes/<date>_<time>.yaml
--map MAP_YAML このマップで map_server と RViz を起動し、マップの参照を記録する
--map-ref MAP_YAML 何も起動せずに、このマップのパスとハッシュを記録する(RViz とマップがすでに起動している場合)
--force FILE が存在する場合に上書きする

その他のサブコマンド:

  • artefacts-route list:保存されたルートを一覧表示します。
  • artefacts-route show:ルートとその問題点を表示します。
  • artefacts-route replay:ルートを RViz のマーカーとしてパブリッシュします。

関数

起動ファイル用ヘルパー:

テスト用ヘルパー:

関数リファレンス

load_route

artefacts-route record で記録したルートを読み込みます。

load_route(
    route=None
)

パラメータ

パラメータ 型 説明 デフォルト
route str または Path ルートファイルへのパス。None の場合、./routes/ から最後に記録されたルートを読み込む None

戻り値

Route:読み込まれたルート。name、frame_id、initial_pose、waypoints、map 属性を持ちます。

例

from artefacts_toolkit.navigation import load_route

route = load_route()                     # 最後に記録されたルート
route = load_route("routes/lab.yaml")    # 特定のルート

route_launch_args

ルートの開始位置にロボットをスポーンさせる起動引数を返します。

route_launch_args(
    route,
    x=None,
    y=None,
    yaw=None,
    names=("x_pose", "y_pose", "yaw"),
    map_arg="map"
)

パラメータ

パラメータ 型 説明 デフォルト
route Route load_route で読み込んだルート 必須
x float 記録された開始位置の代わりに、この x にロボットをスポーンさせる None
y float 記録された開始位置の代わりに、この y にロボットをスポーンさせる None
yaw float 記録された開始位置の代わりに、この向き(ラジアン)でロボットをスポーンさせる None
names Sequence[str] 起動ファイルで宣言しているスポーン位置の引数名(デフォルトと異なる場合) ("x_pose", "y_pose", "yaw")
map_arg str または None マップの起動引数名。None の場合はマップを含めない "map"

戻り値

dict[str, str]:例:{"x_pose": "0.59", "y_pose": "-1.45", "yaw": "1.5708", "map": "/maps/lab.yaml"}

例

from artefacts_toolkit.navigation import load_route, route_launch_args

route = load_route()
args = route_launch_args(route)
IncludeLaunchDescription(my_sim_launch, launch_arguments=args.items())

route_start_pose

ルートの開始位置(記録時の 2D Pose Estimate)を返します。各要素を個別に上書きすることもできます。

route_start_pose(
    route,
    x=None,
    y=None,
    yaw=None
)

パラメータ

パラメータ 型 説明 デフォルト
route Route ルート 必須
x float 開始位置の x を上書きする None
y float 開始位置の y を上書きする None
yaw float 開始位置の yaw(ラジアン)を上書きする None

戻り値

Pose2D:x、y、yaw 属性を持つ開始位置。


check_map_match

map_yaml がルートの記録に使用したマップと異なる場合に UserError を発生させます。誤ったマップでルートを走行させないために役立ちます。

check_map_match(
    route,
    map_yaml,
    ignore_mismatch=False
)

パラメータ

パラメータ 型 説明 デフォルト
route Route ルート 必須
map_yaml str または Path 起動ファイルが読み込むマップ 必須
ignore_mismatch bool マップが異なる場合、エラーの代わりに警告にする False

戻り値

list[str]:警告。ルートに比較対象のマップハッシュがない場合や、不一致を無視した場合など。


map_to_world

Nav2 のマップ(yaml と pgm。実機ロボットで保存したものなど)から押し出したシミュレータ用ワールドを書き出します。Isaac Sim、Newton、MuJoCo、Gazebo に対応しています。 ワールドの生成は比較的軽量(ミリ秒単位)なので、毎回実行時に生成できます。

map_to_world(
    map_yaml,
    out=None,
    format=None,
    wall_height=1.0,
    fill_gaps=True,
    crop=None
)

パラメータ

パラメータ 型 説明 デフォルト
map_yaml str または Path Nav2 のマップ yaml(pgm も同じ場所に必要です) 必須
out str または Path ワールドの書き出し先(必要な場合) worlds/<map name>.<ext>
format str "usda"(Isaac Sim、Newton)、"mjcf"(MuJoCo、Newton)、または "sdf"(Gazebo)。out が指定されている場合はその拡張子から決まる "usda"
wall_height float 壁の高さ(メートル) 1.0
fill_gaps bool 押し出す前に、占有セル間の 2 セル以下の隙間(スキャンノイズ)を埋める True
crop tuple[Route, float] (route, metres):ルートに沿ってロボットが通る経路から指定メートル以内の壁のみを残す。大きなマップでの物理演算を削減する None

戻り値

str:書き出されたワールドファイルの絶対パス。シミュレータに渡します。

例

以下の起動ファイルは、最後に記録されたルートを読み込み、そのマップから Gazebo ワールドを押し出し、ルートの開始位置にロボットを配置した状態で Nav2 の TurtleBot3 デモをそのワールドに含めます:

import os

from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription, OpaqueFunction
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import LaunchConfiguration

from artefacts_toolkit.navigation import load_route, map_to_world, route_launch_args


def include_demo(context):
    route = load_route()  # 最後に記録されたルート
    headless = LaunchConfiguration("headless").perform(context)

    # x_pose、y_pose、yaw、map。デモが使用する名前のまま。
    args = route_launch_args(route)
    args["headless"] = headless
    args["use_rviz"] = str(headless.lower() not in ("true", "1", "yes"))
    # ワールド:ルートのマップを壁として押し出したもの(ミリ秒単位)。
    # これによりシミュレーションがルートの記録に使用したマップと一致する。
    args["world"] = map_to_world(route.map.file, format="sdf")

    demo = os.path.join(
        get_package_share_directory("nav2_bringup"), "launch", "tb3_simulation_launch.py")
    return [IncludeLaunchDescription(
        PythonLaunchDescriptionSource(demo), launch_arguments=args.items())]


def generate_launch_description():
    return LaunchDescription([
        DeclareLaunchArgument(
            "headless", default_value="True", description="no Gazebo GUI and no RViz"),
        OpaqueFunction(function=include_demo),
    ])

follow_route

起動中の Nav2 スタックでルートを走行させ、RouteResult を返します。 テストファイル内で使用します。

follow_route(
    route=None,
    timeout_s=300.0,
    startup_timeout_s=60.0,
    label=None,
    results_dir=None,
    set_initial_pose=True,
    base_frame="base_link",
    scan_topic="scan"
)

パラメータ

パラメータ 型 説明 デフォルト
route str または Path 走行させるルートへのパス(load_route と同様)。None の場合は最後に記録されたルートを走行させる None
timeout_s float または None この秒数を過ぎるとゴールがキャンセルされ、結果は "timeout" になる。None の場合は無期限に待機する 300.0
startup_timeout_s float Nav2 がアクティブになるまで待機する秒数。超過すると EnvError が発生する 60.0
label str 結果とそのファイル名に付加するラベル None
results_dir str または Path 結果を YAML ファイルとして書き出すディレクトリ None
set_initial_pose bool 走行前に、ロボットがルートの開始位置にいることを AMCL に伝える。すでに自己位置推定済みのロボットでは False True
base_frame str 最終位置誤差の計算に使用するロボットのフレーム "base_link"
scan_topic str または None 障害物との距離を計測する LaserScan トピック。None の場合は計測しない "scan"

戻り値

RouteResult:走行の結果。失敗した走行も例外ではなく結果として返されます。

例

以下のテストは、map_to_world の例の起動ファイルをフィクスチャで実行し、最後に記録されたルートを走行させ、その結果に対してアサーションを行います:

import os
import signal
import subprocess
import time
from pathlib import Path

import pytest

from artefacts_toolkit.navigation import (
    assert_each_waypoint_reached, assert_final_pose, assert_max_recoveries,
    assert_min_clearance, follow_route)

HEADLESS = os.environ.get("HEADLESS", "false").lower() not in ("0", "false", "no")
RESULTS_DIR = os.environ.get("ARTEFACTS_SCENARIO_UPLOAD_DIR", "results")
LAUNCH_FILE = Path(__file__).with_name("nav2_sim.launch.py")


@pytest.fixture
def sim(tmp_path):
    """TurtleBot3 デモ。テストごとに起動し、終了後に停止する。"""
    command = ["ros2", "launch", str(LAUNCH_FILE), "headless:={}".format(HEADLESS)]
    with (tmp_path / "sim.log").open("w") as out:
        proc = subprocess.Popen(command, stdout=out, stderr=subprocess.STDOUT,
                                start_new_session=True)
    time.sleep(15)  # Gazebo と Nav2 の起動を待つ
    try:
        yield
    finally:
        os.killpg(proc.pid, signal.SIGINT)  # ros2 launch がプロセスを順番に停止する
        proc.wait(timeout=30)


def test_route_is_completed(sim):
    result = follow_route(timeout_s=300, results_dir=RESULTS_DIR)  # 最後に記録されたルート
    assert_each_waypoint_reached(result, tolerance_m=0.5)
    assert_final_pose(result, max_pos_error_m=0.5)
    assert_min_clearance(result, 0.15)  # LiDAR からの距離。TurtleBot3 の LiDAR は中央付近にある
    assert_max_recoveries(result, 2)

assert_route_completed

Nav2 がルートを完走し、ウェイポイントをスキップしなかった場合を除き、AssertionError を発生させます。

assert_route_completed(
    result
)

パラメータ

パラメータ 型 説明 デフォルト
result RouteResult follow_route の結果 必須

assert_each_waypoint_reached

すべてのウェイポイントに到達し、tolerance_m が指定されている場合はその範囲内に到達した場合を除き、AssertionError を発生させます。

assert_each_waypoint_reached(
    result,
    tolerance_m=None
)

パラメータ

パラメータ 型 説明 デフォルト
result RouteResult follow_route の結果 必須
tolerance_m float 各ウェイポイントにロボットがどれだけ近づく必要があるか None

assert_final_pose

ロボットが最後のウェイポイントの十分近くで停止した場合を除き、AssertionError を発生させます。

assert_final_pose(
    result,
    max_pos_error_m=0.5,
    max_yaw_error_rad=None
)

パラメータ

パラメータ 型 説明 デフォルト
result RouteResult follow_route の結果 必須
max_pos_error_m float 最後のウェイポイントからの最大距離 0.5
max_yaw_error_rad float 最後のウェイポイントからの最大 yaw 誤差 None

assert_min_clearance

いずれかのレーザー測定値が min_m より近かった場合に AssertionError を発生させます。

assert_min_clearance(
    result,
    min_m
)

パラメータ

パラメータ 型 説明 デフォルト
result RouteResult follow_route の結果 必須
min_m float 障害物までの最小許容距離(メートル) 必須

assert_max_recoveries

Nav2 が max_recoveries を超える回数のリカバリー動作(回転、後退、待機など)を実行した場合に AssertionError を発生させます。

assert_max_recoveries(
    result,
    max_recoveries=0
)

パラメータ

パラメータ 型 説明 デフォルト
result RouteResult follow_route の結果 必須
max_recoveries int 許容するリカバリー動作の最大回数 0

RouteResult

1 回の follow_route の走行結果です。

属性 型 説明
succeeded bool Nav2 がルートを完走し、ウェイポイントをスキップしなかった
outcome str "succeeded"、"failed"、"canceled"、または "timeout"
error str または None outcome が "succeeded" でない場合のエラーメッセージ
duration_s float ルート送信から終了までの実時間(秒)
waypoints_total int ルート内のウェイポイント数
missed_waypoints list[int] Nav2 がスキップしたウェイポイントのインデックス
waypoints list[WaypointResult] 各ウェイポイントの結果(下記参照)
final_pos_error_m float または None 終了時のロボットから最後のウェイポイントまでの距離(tf から取得)
final_yaw_error_rad float または None 終了時の最後のウェイポイントとの yaw 誤差
recoveries int ルート全体で Nav2 が実行したリカバリー動作の回数
min_clearance_m float または None ルート全体での最も近いレーザー測定値
warnings list[str] 計測できなかった項目とその理由

result.waypoints 内の各 WaypointResult は以下を持ちます:

属性 型 説明
index int ルート内のウェイポイントのインデックス
reached bool Nav2 が到達したかどうか
time_s float または None ルート開始から到達までの秒数
closest_approach_m float または None 向かう途中でロボットがウェイポイントに最も近づいた距離
recoveries int 向かう途中で Nav2 が実行したリカバリー動作の回数
min_clearance_m float または None 向かう途中での最も近いレーザー測定値

メソッド

  • result.metrics():走行の数値をフラットな dict で返します(duration_s、waypoints_reached、waypoints_total、final_pos_error_m、final_yaw_error_rad、recoveries、min_clearance_m)。Artefacts の metrics.json にそのまま書き出せます。
  • result.save(results_dir):結果を <results_dir>/<route>_<YYYYmmdd-HHMMSS>[_<label>].yaml として書き出し、そのパスを返します。
  • result.summary():走行結果の 1 行の説明。

例

import json

result = follow_route(timeout_s=300)
assert result.succeeded, result.summary()

with open("output/metrics.json", "w") as f:
    json.dump(result.metrics(), f)
最終更新 02.10.2026: toolkit-navigation (#158) (a02abd2)