⏯️

オープンソースのロギング・可視化ツールRerunを使ってみよう

2024/12/18に公開

はじめに

こんにちは、チューリング株式会社でソフトウェアエンジニアをしている矢部(和)です。
今回は、弊社内で最近よく利用されている便利な可視化ツール Rerun を紹介します。
前半はRerunの説明を、後半は実際に簡単なコードを書いてRerunを使ってみたいと思います。

この記事は、社内合宿のテックブログ作成チャレンジで弊社の嶋谷が執筆したRerun解説編を元に、矢部(和)が実践編を追記したものです。


解説編

Rerunとは

Rerunはマルチモーダルデータの管理と視覚化を簡便かつ高性能にするためのツールで、複数の種類のデータを処理、保存、可視化できます。

記録、可視化、データ保存は下記のような流れで行います。

  • Rerun SDKを使ってデータをログとして保存または読み込み
  • ビューアでリアルタイムや記録済みデータを確認
  • UIやSDKで視覚化のカスタマイズ
  • クエリ機能でデータをPandasなどにエクスポート

実際に使ってみると、時間を遡れたりステップ実行ができたり、複数プロセスやネットワークに対応していたり高機能ですが、それでも意外と簡単に使える面白いツールです。

Rerunのインストール

RerunはC++, Python, Rustから使うことができます。

Pythonから使う場合にはpipでインストールできます。

pip install rerun-sdk

デモを見てみよう

このリンクhttps://rerun.io/viewerからブラウザ上でRerunのデモを動かすことができます。

Rerunをインストールしている場合は、下記コマンド実行でもviewerを起動できます。

rerun

マウスでデモを選ぶと、そのまま動かせます。

Rerun viewer screenshot

とりあえずARKIt scenes を試してみましょう。マウスで視点を動かすことができます。

いろいろなデモを試して基本的なviewerの操作を覚えましょう。

記録と表示

データの記録には rr.log() を使います。

rr.log() を使って Viewerへデータを送ると、あとはViewerが自動で可視化して表示してくれます。

rr.log() とは?

公式の説明: https://ref.rerun.io/docs/python/0.20.3/common/logging_functions/

いろいろなパラメータがありますが、まずは一番基本的な使い方は下記のようになります。

  • entity_path でデータが属している階層を指定
  • entity でデータを指定

entity で入れられるデータ

entity で入れられるデータは Archetypes です。

Archetypesはさまざまな種類が用意されています。公式ドキュメントにはexampleとともに詳しく使用方法が書かれています。

以下の例ではそのうちのいくつかを紹介します。

数値

rr.Scaler()を使用します。TimeSeriesViewで表示できます。

可視化の設定はGUIからもできますが、過去の表示設定を覚えていたりなどで期待した表示にならないことがあります。

毎回手動で調節してもよいのですが、以下のようにblueprint機能を使ってプログラムからViewerの可視化設定を行うこともできます。

import math
import time

import rerun as rr
import rerun.blueprint as rrb

rr.init("rerun_example_my_data", spawn=True)

# 可視化方法を以下のようにプログラムからも設定できる
rr.send_blueprint(
    rrb.TimeSeriesView(origin="/scalar")
)

for step in range(0, 64):
    rr.set_time_sequence("step", step)
    rr.log("scalar", rr.Scalar(math.sin(step / 10.0)))
    time.sleep(0.01)

example1

3次元点座標

3次元の点として記録したいときはrr.Points3D()を使用します。

import rerun as rr
import numpy as np

rr.init("rerun_example_my_data", spawn=True)

positions = np.zeros((10, 3))
positions[:,0] = np.linspace(-10,10,10)

rr.log(
    "my_points",
    rr.Points3D(positions, radii=0.5)
)

Position3D

複数のプロセスから記録する

Rerunを起動したプロセスだけでなく、他のプロセスからのログも同時に記録・可視化することができます。以下の2つのプログラムを同時に実行すると、サインカーブとコサインカーブが可視化されます。

プロセス1

import rerun as rr
import numpy as np
import time
import rerun.blueprint as rrb

rr.init("rerun_example_my_data", spawn=True, recording_id="my_shared_recording")
rr.send_blueprint(rrb.TimeSeriesView(contents="curve/**"))

for i in range(100):
    rr.log("curve/sin_curve", rr.Scalar(np.sin(np.pi*2*i/100)))
    time.sleep(0.1)

プロセス2

import rerun as rr
import numpy as np
import time
import rerun.blueprint as rrb

rr.init("rerun_example_my_data", recording_id="my_shared_recording")
rr.connect()

for i in range(100):
    rr.log("curve/cos_curve", rr.Scalar(np.cos(np.pi*2*i/100)))
    time.sleep(0.1)

example2

Overrideの機能

以下のように rr.Spatial3DView()time_range を設定することで、時系列3Dデータを重ねて表示することができます。

import rerun as rr  # NOTE: `rerun`, not `rerun-sdk`!
import numpy as np
import time
import rerun.blueprint as rrb

rr.init("rerun_example_my_data", spawn=True)

positions = np.zeros((10, 3))
positions[:,0] = np.linspace(-10,10,10)
rr.send_blueprint(
    rrb.Spatial3DView(
        origin="/",
        time_ranges=[
            rrb.VisibleTimeRange(
                "time",
                start=rrb.TimeRangeBoundary.cursor_relative(seconds=-8),
                end=rrb.TimeRangeBoundary.cursor_relative()
            )
        ]
    )
)

for i in range(100):
    positions[:,1] = i
    positions[:,2] = i
    rr.log(
        "my_points",
        rr.Points3D(positions, radii=0.5)
    )
    rr.set_time_seconds("time", time.time())
    time.sleep(0.1)

example3

GUIからVisible time range を変更することもできます。

Visible time rage

ネットワークを介した可視化

ネットワーク上のRerun Viewerにデータを送信して可視化もできます。

すでに起動しているViewerへはTCPポートの9876番を使ってデータを送れます。(ポートは起動時のオプションで変更可能です)

あらかじめViewerを起動しておきます。

rerun

rr.connect_tcp() を使ってこのViewerにデータを送れます。接続先はIPアドレスで指定します。 rr.connect_tcp() での名前解決はできません。

import rerun as rr

rr.init("connect to remote viewer")
rr.connect_tcp("127.0.0.1:9876")

rr.log("markdown", rr.TextDocument("# Hello world!", media_type=rr.MediaType.MARKDOWN))

Hello world


実践編1:単調増加グラフ

他にもいろいろな機能がありますが、まずは簡単な例を作成して使ってみたいと思います。

0から99までの値を入れて、単調増加の直線グラフを可視化してみます。

環境を作る

今回はPythonを使ってRerunを動かしてみます。

uvで環境構築します。

uvについての説明は こちら の記事などを見てください。

rerun-exp という名前でプロジェクトを作ります。

なお、今回お試しに使った環境はLinux (Ubuntu 24.04) です。

uv init rerun-exp
cd rerun-exp
uv add rerun-sdk
. .venv/bin/activate

0 から 99 チャレンジ

数値は rr.Scalar() を使います。これを使って 0 から 99 まで入れてみます。

import rerun as rr

rr.init("rerun_example_my_data1", spawn=True)

for x in range(100):
    rr.log("sensor_data", rr.Scalar(x))

単調増加なグラフが表示できました。

直線を期待していましたが、残念なことにガタガタです。 rr.log() を呼び出した時刻がそのまま記録されています。時刻との関係を見たい場合はこれでいいのですが、今回は等間隔にしたいのでもう少し変更します。

monotonically increasing 1

横軸の時間を指定する

rr.set_time_sequence() でデータの時刻を設定できます。

import time
import rerun as rr

rr.init("rerun_example_my_data2", spawn=True)

for x in range(100):
    rr.set_time_sequence("sensor_data", x)
    rr.log("sensor_data", rr.Scalar(x))

直線になりました。

monotonically increasing 2


実践編2:3Dモデルを動かす

もう少し発展的な例として、3DモデルをIMU(慣性計測装置)から得られた姿勢角に合わせて動かす例を作ってみます。

3Dモデルと姿勢角の取得

簡単に試してみるため、姿勢角の検出にはソニー・インタラクティブエンタテインメントから発売されているロボットトイの toio を使ってみました。このロボットトイはIMUを内蔵していて、 姿勢角情報をBluetooth通信で取得 できます。

表示用のデータとして 公開されているtoioの3Dデータ を使います。3DデータはglTF形式のものを使います。glTF形式はデータフォーマットとしてXYZの軸方向が規定されているので扱いやすいです。

toio とのBluetooth通信には 専用のPython向け通信ライブラリ を使い、姿勢角は ジンバルロック の影響を受けたくないのでクォータニオンで取得します。

環境を作る

先程と同様にuvで環境構築します。

使用するパッケージとして、rerun-sdktoio-py を追加しておきます。

toio-py が最新のPythonに対応していないため、Pythonのバージョンは3.12を使っています。

uv init posture
cd posture
uv add rerun-sdk
uv add toio-py
. .venv/bin/activate

toioの3Dモデルデータ もposture ディレクトリに置いてください。

3Dモデルの表示

rr.Asset3D() を使います。 path で3Dモデルデータを指定します。

import rerun as rr

rr.init("posture viewer", spawn=True)
rr.log("world/toio", rr.Asset3D(path="toiocorecube_v003.gltf"))

Assets3D

座標軸の表示

Viewerの座標系と3Dモデルの座標系が合っていないので、モデルがさかさまに表示されています。Viewerに rr.Arrows3D() で座標軸を表示させて向きを確認してみます。

import rerun as rr

rr.init("posture viewer", spawn=True)
rr.log("world/toio", rr.Asset3D(path="toiocorecube_v003.gltf"))

# Arrows3D で座標軸を表示させてみる
colors = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
vectors = [[0.1, 0.0, 0.0], [0.0, 0.1, 0.0], [0.0, 0.0, 0.1]]
origins = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
labels = ["X", "Y", "Z"]
rr.log(
    "world/axis",
    rr.Arrows3D(origins=origins, vectors=vectors, colors=colors, labels=labels),
)

X軸が画面右、Y軸が画面奥、Z軸が画面上方向で表示されています。

toioのモデルデータは NED座標 なので、Viewerの表示もNEDに合わせます。

coordinate system

表示座標系の設定

3D空間の座標系は rr.ViewCoordinates で設定します。

X軸が画面右、Y軸が画面奥、Z軸が画面上方向

rr.ViewCoordinates.FLD です。 FLD は各軸の向き (X=Front, Y=Left, Z=Down) を示しています。Rerun は右手系座標をだけをサポートしています。左手系座標はサポートしていません。

import rerun as rr

rr.init("posture viewer", spawn=True)
rr.log("world/toio", rr.Asset3D(path="toiocorecube_v003.gltf"))
colors = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
vectors = [[0.1, 0.0, 0.0], [0.0, 0.1, 0.0], [0.0, 0.0, 0.1]]
origins = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
labels = ["X", "Y", "Z"]
rr.log(
    "world/axis",
    rr.Arrows3D(origins=origins, vectors=vectors, colors=colors, labels=labels),
)

rr.log("/", rr.ViewCoordinates.FLD, static=True)

向きが正しくなりました。

NED

3Dモデルの位置と向きの変更

3Dモデルの位置や向きを変えるには rr.InstancePoses3D() を使います。

向きは

  • 回転ベクトル
  • クォータニオン
  • 回転行列

のいずれかで指定できます。今回はクォータニオンを使います。

X軸に対して90度回転している状態 (x, y, z, w) ≒ (0.7071, 0, 0, 0.7071) にしてみます。

import rerun as rr

rr.init("posture viewer", spawn=True)
rr.log("world/toio", rr.Asset3D(path="toiocorecube_v003.gltf"))
colors = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
vectors = [[0.1, 0.0, 0.0], [0.0, 0.1, 0.0], [0.0, 0.0, 0.1]]
origins = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
labels = ["X", "Y", "Z"]
rr.log(
    "world/axis",
    rr.Arrows3D(origins=origins, vectors=vectors, colors=colors, labels=labels),
)
rr.log("/", rr.ViewCoordinates.FLD, static=True)

rr.log(
    "world/toio",
    rr.InstancePoses3D(quaternions=rr.Quaternion(xyzw=[0.7071, 0, 0, 0.7071])),
)

回転した状態の3Dモデルが表示されています。

rotation

IMUから得られた姿勢角に合わせて動かしてみる

実際に動かしてみます。

toioとBluetooth通信を行い、通知ハンドラで取得した姿勢角情報を rr.InstansPoses3D() でモデルに与え、向きを変えています。

toioとのBluetooth通信の詳細については本ブログのテーマから外れてしまうので、詳細については省きます。

toio

ソースコードは下記になります。

posture.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import asyncio
from logging import getLogger

import rerun as rr
import rerun.blueprint as rrb
from toio.cube import (
    PostureAngleDetectionCondition,
    PostureAngleDetectionType,
    PostureAngleQuaternionsData,
    Sensor,
    ToioCoreCube,
)

async def log_quaternion_posture():
    count: int = 0

    # Bluetooth 通知ハンドラ内で姿勢角を取得し、Rerun Viewerに送る
    def sensor_handler(payload: bytearray):
        sensor_info = Sensor.is_my_data(payload)
        if isinstance(sensor_info, PostureAngleQuaternionsData):
            x, y, z, w = sensor_info.x, sensor_info.y, sensor_info.z, sensor_info.w
            rr.log(
                "world/toio",
                rr.InstancePoses3D(quaternions=rr.Quaternion(xyzw=[x, y, z, w])),
            )
            nonlocal count
            count += 1

    async with ToioCoreCube() as cube:
        await cube.api.configuration.set_posture_angle_detection(
            PostureAngleDetectionType.Quaternions,
            50,
            PostureAngleDetectionCondition.Always,
        )
        await cube.api.sensor.register_notification_handler(sensor_handler)
        while count < 200:
            await asyncio.sleep(0.1)
        await cube.api.sensor.unregister_notification_handler(sensor_handler)

def setup():
    rr.init("toio posture viewer", spawn=True)
    rr.reset_time()
    rr.log("world/toio", rr.Asset3D(path="toiocorecube_v003.gltf"))
    colors = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
    vectors = [[0.1, 0.0, 0.0], [0.0, 0.1, 0.0], [0.0, 0.0, 0.1]]
    origins = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
    labels = ["X", "Y", "Z"]
    rr.log(
        "world/axis",
        rr.Arrows3D(origins=origins, vectors=vectors, colors=colors, labels=labels),
    )
    rr.log("/", rr.ViewCoordinates.FLD, static=True)

    rr.send_blueprint(rrb.Spatial3DView(origin="/"))

async def main():
    setup()
    await log_quaternion_posture()
    return 0

if __name__ == "__main__":
    exit(asyncio.run(main()))

おわりに

以上が Rerun を使ったリアルタイム情報の可視化例です。

Rerunは動画や点群など、あらゆるデータの可視化に対応しており、リアルタイムな可視化を通じて効率的な開発や高度な技術の実現を支えます。本記事ではその一部しかお見せできませんでしたが、興味をお持ちの方はぜひRerun公式ページもご覧ください。

私たち チューリング株式会社 では、自動運転AI、自動運転システムソフトウェア、そして開発支援ツールの分野で革新的なソフトウェアエンジニアを募集しています。

完全自動運転の実現に興味がある方、最先端の技術開発に挑戦したい方は、ぜひ Turingの採用ページ をご覧ください。

私たちと一緒に未来の自動運転を実現し、人と技術が共存する社会を作りませんか?

あなたのスキルと情熱を、未来への大きな一歩に繋げましょう!

Tech Blog - Turing

Discussion