rerun-chunk-processing について
Rerunのchunk処理パイプラインを、readerや遅延ストリームからlens、RRD出力まで解説するPython向けスキルです。手作業の行処理ではなく、列指向変換とreaderによる取り込みを案内します。
主な機能
- 01遅延chunkストリームの構成
- 02ストリームのフィルター・分割・結合・マッピング
- 03mutateおよびderive lensによるコンポーネント変換
- 04サイドカーデータ用chunkの作成
- 05最適化したRRDの収集と書き込み
活用シーン
- 01対応するデータソースをRRD recordingに変換する
- 02取り込み中にコンポーネントを修正・派生する
- 03取り込んだデータと校正サイドカーを結合する
- 04object-store最適化を適用してcatalog向けRRDを準備する
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rerun-io/ rerun --skill rerun-chunk-processingインストール後、Claude Code で/rerun-chunk-processingを入力して読み込まれたか確認します。
詳細
Rerun チャンク処理
生データと RRD の間にあるパイプライン層です。リーダーが Chunk を生成し、
ストリームが変換し、終端呼び出しが実行されます。このスキルで扱うのは
一般的な仕組みのみです。まずデータモデル(rerun-data-model)を決めてから、
ソースごとにインポータースキルを選んでください。
| ソース | リーダー | スキル |
|---|---|---|
| MCAP ファイル(ROS2、protobuf、Foxglove) | McapReader(path).stream() | rerun-mcap |
| URDF ロボットモデル(+関節状態 → FK) | UrdfTree.from_file_path(...).stream() | rerun-urdf |
| Parquet テーブル(軌道、センサーログ) | ParquetReader(path).stream() | rerun-parquet |
| mp4 カメラ映像 | Mp4Reader(path).stream() | rerun-mp4 |
| HDF5 ファイル(グループ → エンティティ) | Hdf5Reader(path).stream() | まだありません |
| LeRobot データセットディレクトリ | 組み込みインポーター、次に RrdReader | rerun-lerobot |
| 既存の RRD | RrdReader(path) | ここで説明 |
| サイドカーファイル(JSON キャリブレーション、メタデータ) | Chunk.from_columns + from_iter | ここで説明 |
API は rerun.chunk です。動作が重要な場合は、インストール済みのインターフェースを確認してください。
python -c "from rerun.chunk import LazyChunkStream; help(LazyChunkStream)"。
判断基準:各コンポーネントはどこから取得するか?
デフォルト:リーダーがチャンクを生成し、レンズで形を整えます。 変換コードを書く前に この手順を確認してください。「手作業で構築する」という考えは、たいてい誤りです。
- リーダーが対応しているソースですか? リーダーの
.stream()を使ってください。手動で解析して 再ログするのは避けてください。MCAP →McapReader、URDF →UrdfTree、parquet →ParquetReader、 mp4 →Mp4Reader、HDF5 →Hdf5Reader、RRD →RrdReader、 LeRobot ディレクトリ →log_file_from_path。 - デコーダーがすでにアーキタイプを出力していますか? Foxglove は
Transform3D、Pinhole、VideoStream(実際のサンプルバイト列)をそのまま使える形で提供します。 そのまま渡してください。再導出しないでください。カスタム protobuf トピックだけは<Name>:messageとして届き、レンズが必要です(rerun-mcapを参照)。 - 既存コンポーネントをその場で修正しますか(解像度の入れ替え、色の変更、単位変換など)?
MutateLensとoutput_mode="forward_unmatched"を使ってください。 - 新しいコンポーネント/エンティティを導出しますか(FK →
/tf、メッセージからスカラーを取得など)?DeriveLensを使ってください。1 行を N 行に分散させる場合(関節のバッチ → 関節ごとの/tf)は、 2 つのレンズを組み合わせます。まずoutput_mode="forward_all"でバッチを導出し (関節状態などの元データを保持します)、次にscatter=Trueとoutput_mode="drop_unmatched"を指定した 2 つ目のDeriveLensを使います (分散した行だけを出力します)。robot_data_preprocessingの例を参照してください。 - リーダーやレンズでは生成できない、本当に必要なサイドカーですか(JSON キャリブレーションオフセット、
手動計測した外部パラメーター、外部メタデータなど)?
Chunk.from_columns+from_iterを使ってください。 - 最後に
LazyChunkStream.merge(...)→.collect(optimize=OptimizationProfile.OBJECT_STORE)→write_rrd(application_id, recording_id)を実行してください。
この順序にする理由:パイプラインを遅延実行、列指向、マルチスレッドのまま保ち、
OBJECT_STORE による最適化を可能にするためです。手書きの行ループやレンズの外での
pa.array は、その利点をすべて失わせます。意図的に避けているのはこの方法です。
アンチパターン(代わりにリーダー+レンズを使用)
左側の処理を書いている場合は、そこで止めて右側の方法を使用してください。
- 行/コンポーネントを構築する
forループ →Selector(...).pipe(...)を使ったレンズ PyArrow の計算コールバック。 - 変換のためにメッセージごとに
rr.initとrr.logを呼び出す → これは ライブ ロギングです。 取り込みには、リーダーで読み取り、write_rrdを使用してください。 chunk.to_record_batch()とpc.filterを使い、その後Chunk.from_columnsで再構築する (手作業で行を間引く)→stream.drop(content=...)、.split(...)、またはフィルタ済みのpa.arrayを返すMutateLensを使用してください。- レンズの外部で
pa.array/pa.RecordBatch/np.frombufferを組み立てる → 変換処理をMutateLens/DeriveLensのセレクターコールバック内に移してください。 - カスタムパーサーから手作業で組み立てた
rr.send_columns→ 対応する リーダーを使用してください。チャンクが直接生成されます。 - Rerun 以外のライブラリで MCAP/URDF を解析してから再度ログに記録する
→
McapReader/UrdfTreeを使用してください。 - リーダーがすでにデコードするデータに
Chunk.from_columnsを使う (Pinholeの内部パラメーター、VideoStream、transforms トピックからのTransform3D)→ リーダーストリーム内で処理し、必要に応じてMutateLensで修正してください。
pyarrow.compute の「属性不足」型エラーが大量に発生する場合(pc.filter、
pc.list_element など)、通常は pc.* の呼び出しが Selector.pipe のレンズコールバックではなく、モジュールレベルのヘルパー内にあることを意味します。チェッカーを抑制する前にレンズ内にリファクタリングしてください。これらのエラーは、手作業での構築がそもそも不要であることを示しています。
既存のコンバーターを移植していますか? 手作業で構築されたコンバーターはデコーダーの改善より前のものであり、正解の基準ではありません。デコーダーの出力を再確認し(手順 2)、コピーする前に、すべての Chunk.from_columns/for ループをこのリストに照らして確認してください。
コアモデル
LazyChunkStreamは遅延パイプライン DAG であり、コレクションではありません。フィルター、レンズ、マップ、分割、マージを構築しても、ソースデータは読み込まれません。- 実行は終端呼び出し(
write_rrd(...)、collect()、to_chunks()、またはストリームの反復処理)で開始されます。 - 実行はストリーミング方式で、マルチスレッドかつほとんどが GIL の影響を受けません。Python の行ループよりも、ストリーム/レンズ操作と PyArrow の計算を優先してください。
- ムーブセマンティクス:ビルダー呼び出し(
filter、drop、lenses、map、flat_map)は入力ストリームを消費します。消費済みのストリームを再利用するとエラーになります。各ステップの後に再代入してください。終端呼び出しはストリームを消費しませんが、呼び出すたびにパイプライン全体が再実行されます。コストが高い場合は、collect()を一度だけ呼び出してください。 ChunkStoreはメモリ上に実体化されます(stream.collect()、ChunkStore.from_chunks)。LazyStoreはマニフェストでインデックス化され、必要に応じてチャンクを読み込みます(RrdReader(path).store()、カタログのセグメントストア)。どちらもschema()、summary()、stream()、write_rrd(...)を備えています。
ストリームの合成
from rerun.chunk import Chunk, LazyChunkStream, OptimizationProfile
stream.filter(content=, has_timeline=, is_static=, components=)は、各チャンクのうち条件に一致する部分を保持します。stream.drop(...)はその補集合で、同じキーワードフィルターを使用します。contentにはエンティティパスの glob またはそのリストを指定します。stream.map(fn)はChunk -> Chunkを適用し、stream.flat_map(fn)はChunk -> Iterable[Chunk]を適用します。チャンク単位の Python 処理に使う回避手段ですが、列指向の処理にはレンズを優先してください。stream.split(content=, ...)は(matching, non_matching)を返します。両方の分岐で上流を共有します。LazyChunkStream.merge(*streams)は任意の数のソースを集約します。LazyChunkStream.from_iter(chunks)は手作業で構築したチャンクをラップします。
stream = source_stream() # any importer skill
stream = stream.drop(content="/video_raw/**")
stream = stream.lenses(fix_lens, content="/cam/**", output_mode="forward_unmatched")
merged = LazyChunkStream.merge(stream, sidecar_stream)
merged.write_rrd(out_path, application_id="my_app", recording_id=recording_id)
手作業で作成したチャンク — サイドカー専用
Chunk.from_columns を使うのは、リーダーやレンズでは出力できないデータ(JSON/CSV のキャリブレーション、フレームオフセット、外部メタデータ)に限ってください。リーダー(McapReader/UrdfTree/ParquetReader/Hdf5Reader)でトピックをデコードできる場合、またはレンズで導出できる場合は、それが標準的な方法です。ここで手作業で組み立てないでください。
robot_data_preprocessing の例で手作業で作成するチャンクは、JSON オフセットのサイドカーだけです。カメラの修正、FK→/tf、メッシュ、色の再設定はすべてリーダーとレンズで行います。
Chunk.from_columns(entity_path, indexes, columns) は rr.send_columns(...) に対応し、同じ archetype の .columns(...) ヘルパーを受け付けます。
indexes が空の場合は静的データです。
chunk = Chunk.from_columns(
"/tf_static/robot_offsets",
indexes=[], # static
columns=rr.Transform3D.columns(
translation=translations,
quaternion=quaternions_xyzw,
parent_frame=parents,
child_frame=children,
),
)
sidecar_stream = LazyChunkStream.from_iter([chunk])
rr.AnyValues.columns(...) は、標準以外のメタデータフィールドを扱います。確認するには、Chunk の entity_path、num_rows、is_static、timeline_names、to_record_batch()、format()(人が読みやすい表形式)を使用できます。
Recording のプロパティ
Recording のプロパティだけは例外です。手作業で作成しないでください。
Chunk.from_property(name, values) は rr.send_property に対応し、/__properties/<name> に静的チャンクを作成します。
次のようにしてください。
chunk = Chunk.from_property(
"episode",
rr.AnyValues(task="pick", duration_sec=12.4),
)
同じチャンクを手作業で組み立てる代わりに、次のようにします。
Chunk.from_columns(
f"/__properties/{name}",
indexes=[],
columns=rr.AnyValues.columns(**{name: [value]}), # type: ignore[arg-type]
)
/__properties/は Rerun の内部レイアウトです。後者の方法ではこれをハードコードしてしまいます。- フィールド名が動的な場合、
AnyValues.columns()に# type: ignore[arg-type]が必要になります。この抑制が切り替えの合図です。
レンズ
レンズは、行を反復せずにコンポーネントの形状を変更したり、修正したり、導出したりします。stream.lenses(lenses, output_mode=..., content=...) で適用します。
MutateLens(component, selector, keep_row_ids=False)は、既存のコンポーネントをその場で変更します。DeriveLens(component, output_entity=None, scatter=False)は新しい列を作成し、必要に応じて別のエンティティに配置します。出力ごとに.to_component(descriptor, selector)をつなげます。.to_timeline(name, "sequence" | "duration_ns" | "timestamp_ns", selector)は、データ自体から時間列を抽出します。scatter=Trueにすると、入力の 1 行を N 行の出力(リスト要素ごとに 1 行)に展開します。- 同じコンポーネント名が複数のエンティティに存在する場合は、
content=で対象を絞り込みます。
出力モード。デフォルトは drop_unmatched です。
drop_unmatched(デフォルト):レンズの出力だけが残ります。導出のみを行う中間ストリームに適しています。広範囲に適用すると、ほかのデータはすべて暗黙に破棄されます。forward_unmatched:レンズの出力に、どのレンズでも消費されなかった元のコンポーネントを加えたものです。ストリームの残りを維持しながら特定箇所を修正するのに適しています。forward_all:レンズの出力に、DeriveLensの入力も含め、元のコンポーネントをすべて加えたものです。mutate レンズではforward_unmatchedと同じです。
その場で修正する場合(Arrow の型と長さを維持します):
stream = stream.lenses(
MutateLens(
"Pinhole:resolution",
Selector(".").pipe(
lambda res: pa.array(
[(h, w) for w, h in res.to_pylist()],
type=res.type,
)
),
),
content=["/external/cam_low", "/external/cam_high"],
output_mode="forward_unmatched",
)
単位変換を伴う導出(PyArrow の compute を使用し、Python のループは使いません):
DeriveLens("schemas.proto.JointState:message", output_entity="/joints_deg/waist").to_component(
rr.Scalars.descriptor_scalars(),
Selector(".joint_positions").pipe(lambda arr: pc.multiply(pc.list_element(arr, 0), 180.0 / math.pi)),
)
セレクターの構文
Selector("<query>") は jq 形式で、ネストされた Arrow データをたどります。
.は現在の値、.fieldは struct フィールドです。[]はリスト要素を反復し、[N]はリストのインデックスを指定します。?はエラーを抑制して欠落したオプショナル値をスキップし、!は非 null を保証します。|は式から次の式へ値を渡します。
.pipe(fn) は Python/PyArrow の変換処理(または別の Selector)をつなげます。
.execute(array) は式を即時実行します。.execute_per_row(array) は出力行数が入力と一致することを保証します(行の対応関係を維持する必要があるレンズのコールバック内で使用します)。
RRD の書き込み
stream.write_rrd(path, application_id=..., recording_id=...)は、ストリーミングによる 1 回のパスで実行と書き込みを行います。stream.collect(optimize=OptimizationProfile.OBJECT_STORE).write_rrd(...)は、データをメモリに展開してチャンクのレイアウトを最適化した後、書き込みます。メモリ使用量は、メモリに展開したチャンクに応じて増加します。- プロファイル:
OBJECT_STORE(チャンクが大きく、ストレージ/クエリ/カタログ向け)とLIVE(チャンクが小さく、ビューアーの低レイテンシー向け)。 - 複数の物理 RRD は、同じ
recording_idを共有すると、1 つの論理 Recording を構成します。これを使って、ベースデータ、モデル/URDF データ、レイヤーを分けてください。
RRD を Rerun カタログまたは Hub に送る場合は、明示的に別の指定がない限り、必ず OptimizationProfile.OBJECT_STORE を使用してください。
Chunk API と logging API の比較
- Logging(
rr.log、rr.send_columns、RecordingStream)はユーザーコードからのライブロギング向けです。チャンク処理は、既存の記録データの取り込み、変換、後処理向けです。 - Logging → チャンク:RRD を書き込み、
RrdReaderで読み戻します。RrdReader(path)はrecordings()/blueprints()(それぞれkind、application_id、recording_idを持つStoreEntry)を列挙します。順次処理には.stream(store=entry)、インデックスアクセスには.store(store=entry)を使います。 - チャンク → logging:
rerun.send_chunks(chunks, recording=...)は、Chunk、LazyChunkStream、LazyStore、ChunkStore、またはチャンクの任意のイテラブルを受け取ります。ソースストアのapplication_id/recording_idは引き継がれません。アクティブな記録データの ID が優先されます。
よくある注意点
- デフォルトのレンズの
output_modeはdrop_unmatchedです。対象を絞った修正でforward_unmatchedの設定を忘れると、ストリームの残りが警告なく破棄されます。 - 消費済みの
LazyChunkStreamを再利用しないでください。意図的に再割り当てするか、splitしてください。 - レンズは
content=でスコープを指定してください。同じコンポーネント名が複数のエンティティに存在することはよくあります。 MutateLensの変換では、Arrow 配列の型と長さを保持してください。- カタログレイヤーでは、レイヤーの
recording_idがセグメント ID と一致している必要があります。 - これは
rerun.chunkです。アップグレード時にはシグネチャを確認してください。
参考資料
- エンドツーエンドの例(MCAP + URDF + JSON サイドカー、レンズ、マージ、最適化):
https://github.com/rerun-io/rerun/tree/main/examples/python/robot_data_preprocessing - ドキュメント:
https://rerun.io/docs/concepts/logging-and-ingestion/chunk-processing-api、https://rerun.io/docs/concepts/query-and-transform/lenses