フィジカルAI研究室を作る Log#13 | Physical Intelligence の π0.5 がお手本の3か所すべてで箱に入れた

机の上の小さなロボットアームが、AI の指示だけで赤いブロックをつかみ、白い箱に入れる。この動きを、ブロックの置き場所を変えて試しました。アームの左に置いたとき(位置D)も、アームの前に置いたとき(位置E)も、ブロックは箱に入りました。2日前に試した位置C と合わせて、人がお手本を見せた置き場所のうち、試した3か所すべてで箱に入ったことになります。

使った AI は、米国の Physical Intelligence 社が公開しているロボット用のモデル π0.5(パイレイテンゴ)です。上から見たカメラの画像と、6つの関節の角度を受け取り、次の1秒の関節の角度を返します。人が手でアームを動かして教えた動きを、5か所の置き場所で合わせて19回録り、それを見せて学習させました(模倣学習)。前の回の LOG361 では、位置C で初めてブロックが箱に入りました。ただ、それがその1か所だけのたまたまなのかは、まだ分かっていませんでした。

位置D と位置E は、アームにとって条件がかなり違います。位置D はアームの左にあり、つかんでから箱の上まで、土台を約105°回します。位置E はアームの前の手前側で、上のカメラから見ると、ブロックが爪の真下に隠れて、ほとんど写りません(お手本の最初のコマで、赤く写るのは 7〜18 画素)。AI のモデルも学習も、前の回から何も変えていません。AI の答えをアームに届けるプログラムの設定も、前の回で初めて箱に入ったときと同じです。変えたのは、ブロックを置く場所だけです。

ただ、どちらもぎりぎりでした。位置D では、箱の上で爪を開く直前に、ブロックが爪の先から抜けて落ちました。箱の真上だったので、中に入っています。位置E では、ブロックが箱の手前の縁のすぐ上を通りました。位置E は、1回目は爪がブロックの横で閉じてつかみ損ね、2回目で入っています。

記録を数えると、ブロックを持って運ぶ間の肩は、どちらの位置でもお手本より低いところを通っていて、いちばん高いところの差は位置D で 2.9°、位置E で 4.0° でした。 位置D では、AI が送った目標はお手本とほぼ同じ高さで、足りなかったのは、肩が毎回目標より 1.1〜2.8° 低く止まった分です。位置E では、AI の目標そのものがお手本より 2.0° 低く、そこへ止まるときの不足が重なりました。

やったことの全体は次のとおりです。

やったことなぜ結果
電源を入れ直し、手首 j4 が HOME の姿勢に戻るか確かめた
前の回の最後に、手首が +45.87° のまま動かなくなっていたため
アームを待ち受けるプログラム(デーモン)を起動し直すと、HOME まで戻った(j4 +1.31°)
推論用の Pod(時間で借りる GPU マシン)を借り、学習済みのモデルを送ってサーバーを起動した
AI の答えを返す計算をするため
A40 を約1時間、約98円。そのうち約30分は、モデル(6.3GB)を送る待ち時間だった
位置D のお手本と同じ位置にブロックを置き、AI に動かさせた
位置C の成功が、置き場所によらないかを確かめるため
箱に入った。離す直前に、爪の先から抜けて落ちた
位置E で同じことをした
ブロックがカメラにほとんど写らない場所でも動くかを見るため
1回目はつかみ損ね、2回目で入った。箱の縁のすぐ上を通った
運ぶ間の肩の角度を、お手本と比べた
ぎりぎりになった理由を数字で見るため
肩のいちばん高いところが、お手本より D で 2.9°、E で 4.0° 低かった
位置E の回を横から撮った動画のコマ。66秒で爪を閉じ、90秒で持ち上げ、98秒でブロックが箱の手前の縁のすぐ上を通り、135秒には離したブロックが箱の中にある

この回の作業も、Claude(この記事の手順と分析を一緒に組み立てた AI)と進めました。手順・分析・記録の集計は Claude が行い、アームの操作と、目で見た判断・止める判断はこちらで行っています。プログラムの変更はしていません。ここから下は、使ったコマンド・コード・出力を全量で載せた作業ログです。ログの中の Pod の IP アドレスとポート番号は、<POD_IP> と <SSH_PORT> に置き換えています。費用の円は 1ドル=158円 で計算しています。結果と効果の数字は、どれも記録から数えた値です。見込みのものは、見込みと書いています。

作業ログ ― 手首 j4 は、デーモンを起動し直すと HOME の姿勢に戻った

前の回の最後に、手首 j4 が +45.87° のまま動かなくなり、HOME の姿勢(全関節がほぼ 0°)に戻せなくなっていました。この回は、アームの電源を入れ直してから始めています。

電源を入れ直したあと、HOME に戻すスクリプトを動かすと、まず Connection refused で止まりました。アームの中の Raspberry Pi で、角度の指令を受け取って関節へ送るプログラム(デーモン、5555番で待ち受ける)は、電源を入れ直すと止まります。それを起動し直してから、もう一度 HOME に戻しました。

Last login: Sat Oct 24 10:23:26 on ttys003
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % cd ~/r2r2r-scripts
python3 dynamic_tracking_limits/place/place_modules/08_home_from_anywhere.py
[INIT] daemon 接続中...
Traceback (most recent call last):
  File "/Users/shimazueiichi/r2r2r-scripts/dynamic_tracking_limits/place/place_modules/08_home_from_anywhere.py", line 97, in <module>
    main()
  File "/Users/shimazueiichi/r2r2r-scripts/dynamic_tracking_limits/place/place_modules/08_home_from_anywhere.py", line 37, in main
    get_daemon_socket()
  File "/Users/shimazueiichi/r2r2r-scripts/visual_servo_pick.py", line 528, in get_daemon_socket
    s.connect((DAEMON_HOST, DAEMON_PORT))
ConnectionRefusedError: [Errno 61] Connection refused
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % ssh -i ~/.ssh/id_rsa_pi ubuntu@192.168.2.1 "setsid nohup python3 /home/ubuntu/mecharm_daemon.py </dev/null >>/home/ubuntu/daemon.log 2>&1 &"; sleep 8; ssh -i ~/.ssh/id_rsa_pi ubuntu@192.168.2.1 'tail -3 ~/daemon.log; ss -ltn | grep 5555'
[DAEMON 14:42:21] ✓ MyCobot 初期化完了
[DAEMON 14:42:21] startup_warmup angles=[1.66, 92.81, 0.61, 0.35, -95.27, -29.44]
[DAEMON 14:42:21] ポート 5555 で待ち受け中...
LISTEN   0         1                   0.0.0.0:5555             0.0.0.0:*       
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % python3 dynamic_tracking_limits/place/place_modules/08_home_from_anywhere.py
[INIT] daemon 接続中...
  [WARN] fresh_mode切替不可: daemon error: set_fresh_mode is not available in this pymycobot environment
[INIT] fresh_mode=1
[PRE]  現在姿勢: [1.66, 92.81, 0.61, 0.35, -95.27, -29.44]
[SEND] HOME へ移動指令
[WAIT] t=2.1s  max_err=11.33°
[WAIT] t=2.6s  max_err=1.40°
[DONE] HOME 到達 max_err=1.40°
[POST] 最終姿勢: [0.17, 1.31, 1.40, 0.26, 1.31, -29.53]
  [WARN] fresh_mode切替不可: daemon error: set_fresh_mode is not available in this pymycobot environment
[EXIT] fresh_mode=0, socket close

デーモンが起動したときの姿勢は [1.66, 92.81, 0.61, 0.35, -95.27, -29.44] で、電源が切れている間に、肩 j1 と手首 j4 が重さで大きく倒れていました。HOME の指令を送ると 2.6 秒で着き、手首 j4 は +1.31° になりました。前の回の +45.87° のずれは、残っていません。

推論用の Pod を借り、モデル 6.3GB を約30分かけて送った

推論は、Runpod で借りた Pod(時間で借りる GPU マシン)で動かしました。GPU は A40 を1枚、料金は1時間 0.62ドル(約98円)です。前の回(LOG361)の夕方と同じ手順で、Mac から、mechArm 用のスクリプト一式・学習設定の追加スクリプト・学習済みのモデル(KIOXIA の checkpoints/pi05_mecharm_trim_lora_v1/3999/、32ファイル・6,340,573,809 バイト)を送りました。

Mac のターミナルで打ったコマンドです。

P=<SSH_PORT>; IP=<POD_IP>; K=~/.ssh/id_ed25519
ssh -p $P -i $K root@$IP "mkdir -p /workspace/r2r2r /workspace/logs"
scp -P $P -i $K -r ~/r2r2r-scripts/pi05 root@$IP:/workspace/r2r2r/
scp -P $P -i $K /Volumes/KIOXIA/r2r2r-data/lab/Log11/pod/add_trim_config.py root@$IP:/workspace/r2r2r/
cd /Volumes/KIOXIA/r2r2r-data/checkpoints/pi05_mecharm_trim_lora_v1/3999
find . -type d | ssh -p $P -i $K root@$IP "mkdir -p /workspace/ckpt/3999 && cd /workspace/ckpt/3999 && xargs mkdir -p"
find . -type f -exec stat -f '%z %N' {} + | sort -rn | cut -d' ' -f2- | xargs -P 4 -I{} scp -q -P $P -i $K {} root@$IP:/workspace/ckpt/3999/{}
ssh -p $P -i $K root@$IP "cd /workspace/ckpt/3999 && find . -type f -printf '%s %p\n'" > /tmp/remote_ck.txt
find . -type f | while read f; do s=$(stat -f %z "$f"); grep -qx "$s $f" /tmp/remote_ck.txt || echo "不一致: $f"; done; echo "確認おわり"

そのときの出力です。モデルは -q(進み具合を出さない)で送っているので、スクリプトを送った行のあとは、送り終わるまで何も出ません。

(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % P=<SSH_PORT>; IP=<POD_IP>; K=~/.ssh/id_ed25519
ssh -p $P -i $K root@$IP "mkdir -p /workspace/r2r2r /workspace/logs"
scp -P $P -i $K -r ~/r2r2r-scripts/pi05 root@$IP:/workspace/r2r2r/
scp -P $P -i $K /Volumes/KIOXIA/r2r2r-data/lab/Log11/pod/add_trim_config.py root@$IP:/workspace/r2r2r/
cd /Volumes/KIOXIA/r2r2r-data/checkpoints/pi05_mecharm_trim_lora_v1/3999
find . -type d | ssh -p $P -i $K root@$IP "mkdir -p /workspace/ckpt/3999 && cd /workspace/ckpt/3999 && xargs mkdir -p"
find . -type f -exec stat -f '%z %N' {} + | sort -rn | cut -d' ' -f2- | xargs -P 4 -I{} scp -q -P $P -i $K {} root@$IP:/workspace/ckpt/3999/{}
ssh -p $P -i $K root@$IP "cd /workspace/ckpt/3999 && find . -type f -printf '%s %p\n'" > /tmp/remote_ck.txt
find . -type f | while read f; do s=$(stat -f %z "$f"); grep -qx "$s $f" /tmp/remote_ck.txt || echo "不一致: $f"; done; echo "確認おわり"
The authenticity of host '[<POD_IP>]:<SSH_PORT> ([<POD_IP>]:<SSH_PORT>)' can't be established.
ED25519 key fingerprint is: SHA256:biMgaaE6vvD2BAFaeXtdrn1BUngh5/6qiskLLrvkkOE
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[<POD_IP>]:<SSH_PORT>' (ED25519) to the list of known hosts.
eval_policy_offline.py                        100% 9921    35.6KB/s   00:00    
convert_to_lerobot.py                         100% 2873    10.4KB/s   00:00    
run_policy_client.py                          100%   44KB  71.0KB/s   00:00    
check_dataset.py                              100%   14KB  52.1KB/s   00:00    
build_demo_episodes.py.bak_pre_LOG352_2026101 100%   11KB  40.5KB/s   00:00    
RUNPOD_TRAIN.md                               100%   15KB  53.1KB/s   00:00    
install_addon.py                              100% 4786    13.1KB/s   00:00    
mecharm_policy.py                             100% 2321     8.5KB/s   00:00    
eval_policy_offline.cpython-312.pyc           100%   15KB  56.7KB/s   00:00    
build_demo_episodes.cpython-310.pyc           100%   11KB  39.5KB/s   00:00    
build_demo_episodes.cpython-312.pyc           100%   29KB 105.9KB/s   00:00    
check_dataset.cpython-312.pyc                 100%   25KB  93.6KB/s   00:00    
policy_ws.cpython-312.pyc                     100% 4580    16.6KB/s   00:00    
camera_align.cpython-312.pyc                  100%   18KB  67.5KB/s   00:00    
run_policy_client.cpython-312.pyc             100%   49KB 168.3KB/s   00:00    
build_demo_episodes.py                        100%   24KB  89.1KB/s   00:00    
policy_ws.py                                  100% 2778    10.1KB/s   00:00    
camera_align.py                               100%   13KB  48.5KB/s   00:00    
add_trim_config.py                            100% 1966     6.8KB/s   00:00    

送っている間に、Pod に入って tmux(接続が切れても作業が続く画面)を開き、その中で openpi(π0.5 を動かすための公開コード)を前の回と同じ版(コミット 215abfb)で取り直しました。

tmux new -s p
cd /workspace
git clone --recurse-submodules https://github.com/Physical-Intelligence/openpi.git
cd openpi && git checkout 215abfb
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
GIT_LFS_SKIP_SMUDGE=1 uv sync

Pod の画面に残っていた、uv sync の最後の部分です。

root@cda8d55e10f9:/# tmux new -s p

 + transformers==4.53.2
 + treescope==0.1.9
 + triton==3.3.1
 + typeguard==4.4.2
 + typing-extensions==4.13.2
 + typing-inspect==0.9.0
 + typing-inspection==0.4.1
 + tyro==0.9.22
 + tzdata==2025.2
 + urllib3==2.4.0
 + virtualenv==20.31.2
 + wadler-lindig==0.1.6
 + wandb==0.19.11
 + websockets==15.0.1
 + werkzeug==3.1.3
 + widgetsnbextension==4.0.14
 + wrapt==1.14.1
 + xxhash==3.5.0
 + yarl==1.20.0
 + zarr==3.0.8
 + zipp==3.22.0
root@cda8d55e10f9:/workspace/openpi# 
[p] 0:uv*                                         "cda8d55e10f9" 09:26 24-Oct-26

モデルを送り始めてから15分ほどたったところで、Pod 側に届いた量を見ました。

root@cda8d55e10f9:/workspace/openpi# du -sb /workspace/ckpt/3999
3491538720      /workspace/ckpt/3999
root@cda8d55e10f9:/workspace/openpi#

全体 6,340,573,809 バイトのうち 3,491,538,720 バイト(約55%)で、送る速さは1秒あたり約 3.9MB でした。確認おわり が出たのは送り始めてから約30分後で、不一致 の行は出ていません。Pod を借りていた約1時間のうち、半分はこの送る待ち時間でした。

送り終わったあと、Pod の tmux の中でサーバーを起動しました。前の回と同じコマンドです。

python3 /workspace/r2r2r/pi05/openpi_addon/install_addon.py /workspace/openpi
python3 /workspace/r2r2r/add_trim_config.py /workspace/openpi
cd /workspace/openpi
mkdir -p assets/pi05_mecharm_trim_lora/local && cp -r /workspace/ckpt/3999/assets/local/mecharm_redblock_box_teach_v1_trim2 assets/pi05_mecharm_trim_lora/local/
uv run scripts/serve_policy.py policy:checkpoint --policy.config=pi05_mecharm_trim_lora --policy.dir=/workspace/ckpt/3999 2>&1 | tee /workspace/logs/serve.log

Mac からは、Pod の 8000 番を手元の 8000 番へつなぐ SSH のトンネルを張り、アームを動かすクライアントは ws://localhost:8000 に問い合わせます。

ssh -N -L 8000:localhost:8000 -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP>

サーバーのログ serve.log(KIOXIA の lab/Log13/serve.log)

Pod を消す前に持ち帰ったサーバーのログです。時刻は Pod の時計(世界標準時)です。Loaded norm stats from …/mecharm_redblock_box_teach_v1_trim2 の2行で、学習に使ったお手本(trim2)から計算した正規化の値を読んだことが分かります。connection open と closed の組は、Mac のクライアントが1回つなぐたびに出る行で、3組あります(位置D の回、位置E の1回目、位置E の2回目)。

INFO:root:Loading model...
INFO:2026-10-24 09:57:33,134:jax._src.xla_bridge:925: Unable to initialize backend 'rocm': module 'jaxlib.xla_extension' has no attribute 'GpuAllocatorConfig'
INFO:jax._src.xla_bridge:Unable to initialize backend 'rocm': module 'jaxlib.xla_extension' has no attribute 'GpuAllocatorConfig'
INFO:2026-10-24 09:57:33,136:jax._src.xla_bridge:925: Unable to initialize backend 'tpu': INTERNAL: Failed to open libtpu.so: libtpu.so: cannot open shared object file: No such file or directory
INFO:jax._src.xla_bridge:Unable to initialize backend 'tpu': INTERNAL: Failed to open libtpu.so: libtpu.so: cannot open shared object file: No such file or directory
INFO:absl:orbax-checkpoint version: 0.11.13
INFO:absl:Created BasePyTreeCheckpointHandler: use_ocdbt=True, use_zarr3=False, pytree_metadata_options=PyTreeMetadataOptions(support_rich_types=False), array_metadata_store=<orbax.checkpoint._src.metadata.array_metadata_store.Store object at 0x70592001c490>
INFO:absl:Restoring checkpoint from /workspace/ckpt/3999/params.
INFO:absl:[thread=MainThread] Failed to get flag value for EXPERIMENTAL_ORBAX_USE_DISTRIBUTED_PROCESS_ID.
INFO:absl:[process=0] /jax/checkpoint/read/bytes_per_sec: 1.3 GiB/s (total bytes: 6.3 GiB) (time elapsed: 5 seconds) (per-host)
INFO:absl:Finished restoring checkpoint in 5.02 seconds from /workspace/ckpt/3999/params.
INFO:openpi.shared.download:Downloading gs://big_vision/paligemma_tokenizer.model to /root/.cache/openpi/big_vision/paligemma_tokenizer.model
INFO:tqdm_loggable.tqdm_logging:Progress on: -/4264023 rate:- remaining:? elapsed:00:00 postfix:-
INFO:tqdm_loggable.tqdm_logging:Progress on: 4.07MiB/4.07MiB rate:4,261,478.9iB/s remaining:00:00 elapsed:00:01 postfix:-
INFO:root:Loaded norm stats from /workspace/openpi/assets/pi05_mecharm_trim_lora/local/mecharm_redblock_box_teach_v1_trim2
INFO:root:Loaded norm stats from /workspace/ckpt/3999/assets/local/mecharm_redblock_box_teach_v1_trim2
INFO:root:Creating server (host: cda8d55e10f9, ip: 172.28.0.2)
INFO:websockets.server:server listening on 0.0.0.0:8000
INFO:websockets.server:connection open
INFO:openpi.serving.websocket_policy_server:Connection from ('127.0.0.1', 58928) opened
INFO:openpi.serving.websocket_policy_server:Connection from ('127.0.0.1', 58928) closed
INFO:websockets.server:connection open
INFO:openpi.serving.websocket_policy_server:Connection from ('127.0.0.1', 60202) opened
INFO:openpi.serving.websocket_policy_server:Connection from ('127.0.0.1', 60202) closed
INFO:websockets.server:connection open
INFO:openpi.serving.websocket_policy_server:Connection from ('127.0.0.1', 48308) opened
INFO:openpi.serving.websocket_policy_server:Connection from ('127.0.0.1', 48308) closed

位置D:お手本と同じ位置にブロックを置き、AI に動かさせた

この記事のコード・ログ・データは、1ファイルずつ下に置いています。全文はリンク先にあります。

ファイルの一覧(5本)

01_sec03.txt ― 位置D の回(19:02・pi05_20261024_190239)のターミナルの出力・22,286字

02_sec04.txt ― 2回目(19:16・pi05_20261024_191603)のターミナルの出力・18,159字

03_de_vs_demo.py.txt ― お手本と比べたスクリプト de_vs_demo.py・4,071字

04_out_de_vs_demo.txt ― 出力 out_de_vs_demo.txt・7,096字

05_out_run_chunk_tables.txt ― 出力 out_run_chunk_tables.txt・9,307字

位置D は、アームの左側です。お手本は5本あり、どれもつかんだときの土台 j0 は −61.5〜−61.7° です。ブロックを置くときは、前の回に作った camera_align.py の --episode で、上のカメラのライブ映像に、お手本(run_0001_20261019_193404)の最初のコマを重ね、写っているブロックに実物のブロックを合わせました。

cd ~/r2r2r-scripts
RAW=/Volumes/KIOXIA/r2r2r-data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes
python3 pi05/camera_align.py --raw-dir $RAW --episode run_0001_20261019_193404 --scale 5

この画面の出力は、最後の数行しか残っていません。

nse>=0.20)
^C
[STOP  ] Ctrl-C
[DONE  ] 終了 (何も書いていません)

横からの動画を撮り始めてから、前の回の 18:16 の回(π0.5 が初めて箱に入れた回)と同じオプションでアームを動かしました。--via-stride 3 は、1秒分の答えのうち途中の姿勢も3ステップおきに送るもの、--state-from-target は、AI に渡すいまの関節の角度を、直前にアームへ送った目標の角度にするものです(どちらも LOG361 で足したもの)。

位置D の回(19:02・pi05_20261024_190239)のターミナルの出力

01_sec03.txt22,286字・273行・text
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % python3 pi05/run_policy_client.py --raw-dir $RAW --steps-per-chunk 10 --via-stride 3 --settle-retries 0 --state-from-target --execute
[SAFE  ] 安全範囲 (学習 19 本 ±10°): j0[-72,+55]  j1[-9,+88]  j2[-46,+41]  j3[-18,+27]  j4[-90,+10]  j5[-51,-14]
[SAFE  ] 肩 j1 の上限は J1_SAFE_MAX=88° で切った (これを超える指令はアームが黙って捨てる / LOG352)  元データ: /Volumes/KIOXIA/r2r2r-data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes
[MODE  ] EXECUTE  steps/chunk=10  max_move=20.0°  log=/Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261024_190239
[CAMERA] 写り方 dy=+0.3 dx=+1.4 response=0.574 → OK (|dy|<=20 / |dx|<=20 / response>=0.20)
[CAMERA] 基準: /Volumes/KIOXIA/r2r2r-data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes/run_0001_20261019_183454/frames/000000.jpg
[POLICY] 接続 ws://localhost:8000  metadata={}
[GRIP  ] 開始時のグリッパー開放は, 最初の目標が検査を通ってから行います (爪が閉じていたら手で開けておく)
別のタブで開く保存

チャンク(1回の問い合わせで返る1秒分の動き)04 で爪を閉じ、05〜06 でその場で肩を上げ、07〜10 で土台を −58° から +42° まで回して箱の上へ運び、11 で肩を下げて爪を開きました。12〜15 で HOME の姿勢に戻り、16 でまたつかみに行こうとしたところを h で止めています。

横から撮った動画では、箱の上で、離す前にブロックが落ちた

位置D の回を横から撮った動画のコマ。61秒で爪を閉じ、117秒で持ち上げ、133秒で箱の方へ回り、145秒には、離す前に落ちたブロックが箱の中にある

動画を見て、こう返しました。

入ったけど、所定の位置で離す前に落ちちゃった。

動画の 137.5〜140.5 秒を、0.5秒ずつ並べました。139.5 秒で、ブロックが爪の先から斜めに抜けて、箱の中へ落ちています。

位置D の回の動画の 137.5〜140.5 秒。139.5 秒でブロックが爪の先から斜めに抜け、140.5 秒には箱の中にある

ログと合わせると、落ちたのはチャンク10 の途中です。チャンク10 は、土台を +28.6° から +42.4° へ回して止め、同時に肩を下げ始める(目標の j1 が 46.1° から 52.6° へ)動きでした。爪を開く指令は、その次のチャンク11 で出ています。

上のカメラで見ると、ブロックは爪の先のほうでつかまれていた

アームを動かすクライアントは、問い合わせのたびに上のカメラのコマを残しています。チャンク10 の頭ではまだ爪の中にあり、チャンク11 の頭ではもうありません。運んでいる間のコマでは、ブロックが爪の奥ではなく、先のほうに挟まっているように見えます。

位置D の回の上カメラのコマ。チャンク05(閉じた直後)、09(運んでいる途中)、10(まだ持っている)、11(もう爪の中にない)。09 と 10 では、ブロックが爪の先のほうに挟まっている

つかんだ姿勢はお手本とほぼ同じですが、土台 j0 が 2.0° ずれていました(お手本 −61.6°、この回 −59.6°)。上のカメラで測ると、ブロックを置いた位置も、お手本より、左の端からも下の端からも 8〜9mm 離れていました(このあとの節)。浅くつかんだことが、この 2° や置いた位置のずれから来たのかは、まだ確かめていません。

位置E:1回目は爪がブロックの横で閉じ、2回目で箱に入った

位置E は、アームの前の手前側です。お手本は4本で、つかんだときの土台 j0 は +12.8〜+13.2° です。HOME の姿勢の爪のほぼ真下にあるので、上のカメラでは、ブロックが爪の陰に隠れます。ブロックを置くときは、重ねた画像が見えるように --alpha 0.7 を付けました。

python3 dynamic_tracking_limits/place/place_modules/08_home_from_anywhere.py
python3 -c "from visual_servo_pick import get_daemon_socket, daemon_set_gripper, close_daemon_socket; get_daemon_socket(); daemon_set_gripper(0); close_daemon_socket()"
python3 pi05/camera_align.py --raw-dir $RAW --episode run_0001_20261019_194818 --scale 5 --alpha 0.7

1回目(19:13)は爪がブロックの横で閉じ、19:14 の回は上のカメラの確認で止まった

位置E では、成功した回の前に2回動かしていました。この2回のターミナルの出力は残していないので、クライアントが KIOXIA に残した記録(runs/pi05_policy/ の log.jsonl と上カメラのコマ)から書きます。全チャンクの表は、記事の最後の節に載せています。

記録何が起きたか
pi05_20261024_191309(19:13)
上カメラの写り方は OK(dy −2.36・dx +3.39・response 0.577)。チャンク04 で爪を閉じたが、上カメラのコマでは、ブロックは爪の外の右下にあった。チャンク05 は送らずに h で HOME に戻して止めた
pi05_20261024_191455(19:14)
上カメラの写り方の確認で、判定が 測れない になり(dy +4.26・dx +13.98・response −0.012)、アームには何も送らずに終わった

19:13 の回の最初のコマでは、赤く写るブロックが 89 画素ありました。お手本4本の最初のコマは 7〜18 画素(LOG361 で測った値)で、成功した 19:16 の回は 37 画素です。爪の陰から外れて見えていた分だけ、ブロックがお手本の位置からずれていたことになります。

2回目(19:16・pi05_20261024_191603)のターミナルの出力

02_sec04.txt18,159字・223行・text
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % python3 pi05/run_policy_client.py --raw-dir $RAW --steps-per-chunk 10 --via-stride 3 --settle-retries 0 --state-from-target --execute
[SAFE  ] 安全範囲 (学習 19 本 ±10°): j0[-72,+55]  j1[-9,+88]  j2[-46,+41]  j3[-18,+27]  j4[-90,+10]  j5[-51,-14]
[SAFE  ] 肩 j1 の上限は J1_SAFE_MAX=88° で切った (これを超える指令はアームが黙って捨てる / LOG352)  元データ: /Volumes/KIOXIA/r2r2r-data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes
[MODE  ] EXECUTE  steps/chunk=10  max_move=20.0°  log=/Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261024_191603
[CAMERA] 写り方 dy=-4.7 dx=+6.9 response=0.585 → OK (|dy|<=20 / |dx|<=20 / response>=0.20)
[CAMERA] 基準: /Volumes/KIOXIA/r2r2r-data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes/run_0001_20261019_183454/frames/000000.jpg
[POLICY] 接続 ws://localhost:8000  metadata={}
[GRIP  ] 開始時のグリッパー開放は, 最初の目標が検査を通ってから行います (爪が閉じていたら手で開けておく)
別のタブで開く保存

チャンク04 で爪を閉じ、05 でその場で肩を上げ、06〜07 で土台を +12° から +43° まで回して箱の上へ運び、08 で肩を下げて爪を開きました。09〜12 で HOME の姿勢に戻り、13 は q で止めています。最初の問い合わせが 877ms で返っているのは、19:13 の回でサーバー側のコンパイルが済んでいたためです(位置D の最初の問い合わせは 56,893ms)。

横から撮った動画では、ブロックが箱の手前の縁のすぐ上を通った

動画を見て、こう返しました。

超難関のE入るのすごいわ(ちょっとギリギリだったけど)

96〜104 秒を並べると、爪につかんだブロックの下の面が、箱の手前の縁とほぼ同じ高さを通っています。

位置E の2回目の動画の 96・98・100・104 秒。98 秒でブロックの下の面が箱の手前の縁とほぼ同じ高さにあり、104 秒で縁を越えている

ブロックを置いた位置は、お手本と 1cm 以内で合っていた

上のカメラの最初のコマで、赤いブロックの中心がマット(620mm × 450mm)の端から何 mm にあるかを測りました。LOG361 の block_position_mm.py に、この回の3つの回を足したものです。

上のカメラの最初のコマ。上の段はお手本と実機の位置D、下の段はお手本と実機の位置E。位置E のブロックは、どちらも爪の陰にほとんど隠れている

ブロックの位置を測るスクリプト block_position_mm.py

# お手本と実機の回 (LOG363) の最初のコマで、赤いブロックの中心をマットの端からの距離 (mm) にする
#  LOG361 の block_position_mm.py に、2026-10-24 の実機の回 (位置D・位置E) の最初のコマを足したもの
#  マットの横幅 620mm を画像の左右の端の間の画素数で割って 1 画素の大きさを出す
import os
import cv2, numpy as np
ROOT = os.environ.get("R2R2R_DATA", "/Volumes/KIOXIA/r2r2r-data"); os.chdir(ROOT)
R = "datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes"
def mat_edges(im):
    g = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY); dark = g < 90
    L, Rr = [], []
    for y in range(120, 180):
        xs = np.where(dark[y])[0]; L.append(xs.min()); Rr.append(xs.max())
    yb = np.median([np.where(dark[:, x])[0].max() for x in range(70, 110)])
    return np.median(L), np.median(Rr), yb
def block(im, s_min, v_min):
    hsv = cv2.cvtColor(im, cv2.COLOR_BGR2HSV)
    m = (((hsv[..., 0] < 10) | (hsv[..., 0] > 165)) & (hsv[..., 1] > s_min) & (hsv[..., 2] > v_min)).astype(np.uint8)
    m[:60] = 0
    n, lab, st, cen = cv2.connectedComponentsWithStats(m); k = 1 + np.argmax(st[1:, 4])
    pts = np.column_stack(np.where(lab == k))[:, ::-1].astype(np.float32)
    (_, _), (bw, bh), ang = cv2.minAreaRect(pts)
    return cen[k], st[k, 4], ang, bw, bh
RUNS = "runs/pi05_policy"
for name, path, s_min, v_min in [("D お手本", f"{R}/run_0001_20261019_193404/frames/000000.jpg", 70, 50),
                                 ("D お手本", f"{R}/run_0001_20261019_193932/frames/000000.jpg", 70, 50),
                                 ("D 実機 190239", f"{RUNS}/pi05_20261024_190239/frames/0000.jpg", 70, 50),
                                 ("E お手本", f"{R}/run_0001_20261019_194818/frames/000000.jpg", 120, 80),
                                 ("E お手本", f"{R}/run_0001_20261019_195120/frames/000000.jpg", 120, 80),
                                 ("E 実機 191309", f"{RUNS}/pi05_20261024_191309/frames/0000.jpg", 120, 80),
                                 ("E 実機 191603", f"{RUNS}/pi05_20261024_191603/frames/0000.jpg", 120, 80)]:
    im = cv2.imread(path)
    xl, xr, yb = mat_edges(im); mm = 620 / (xr - xl)
    (cx, cy), area, ang, bw, bh = block(im, s_min, v_min)
    print(f"{name:14s}  1画素 {mm:.2f}mm  画素数 {area:3d}  左の端から {(cx-xl)*mm:4.0f}mm  下の端から {(yb-cy)*mm:4.0f}mm  向き {ang:5.0f}°  {bw*mm:.0f}x{bh*mm:.0f}mm")

出力 out_block_position_mm.txt

D お手本           1画素 2.23mm  画素数 253  左の端から   63mm  下の端から  244mm  向き   -22°  37x38mm
D お手本           1画素 2.24mm  画素数 253  左の端から   62mm  下の端から  243mm  向き   -31°  37x39mm
D 実機 190239     1画素 2.24mm  画素数 251  左の端から   71mm  下の端から  252mm  向き   -23°  37x39mm
E お手本           1画素 2.23mm  画素数  18  左の端から  378mm  下の端から  103mm  向き   -90°  11x7mm
E お手本           1画素 2.24mm  画素数  14  左の端から  373mm  下の端から   99mm  向き   -76°  11x7mm
E 実機 191309     1画素 2.24mm  画素数  89  左の端から  383mm  下の端から  113mm  向き   -11°  21x28mm
E 実機 191603     1画素 2.23mm  画素数  37  左の端から  379mm  下の端から  113mm  向き   -63°  20x11mm

位置D は、お手本より左の端から 8〜9mm、下の端から 8〜9mm 離れた位置に置いていました(マットの下の端は、カメラで見てアームと反対の側)。位置E は、ブロックがほとんど爪の陰に隠れていて、見えている部分の中心しか測れないので、この数字は位置の目安になりません。

運ぶ間の肩は、お手本より位置D で 2.9°、位置E で 4.0° 低かった

どちらの回もぎりぎりだったので、ブロックを持っている間の動きを、同じ位置のお手本と比べました。アームを動かすクライアントは、中継点(1秒分の答えの途中の姿勢)ごとに、送った目標と、着いたあとに測った角度を残しています。肩 j1 は、数字が小さいほど腕が上がります。

お手本の肩の最高AI が送った目標の最高着いたあとの実測の最高
目標と実測の差(持っている間の中継点)
位置D45.9°(5本とも)46.1°(+0.2°)48.8°(+2.9°)
28点、+1.08〜+2.78°、平均 +2.07°
位置E46.3〜46.4°(4本)48.3°(+2.0°)50.3°(+4.0°)
16点、+0.59〜+2.06°、平均 +1.24°

位置D では、AI の目標はお手本とほとんど同じ高さを通っていて、足りなかったのは、ブロックを持った肩が目標より毎回低く止まった分です。前の回で、ブロックを持って肩を上げる指令43回が43回とも目標より低く止まっていたのと、同じ傾向です。位置E では、AI の目標そのものがお手本より 2.0° 低いところまでしか上がらず、そこへ止まるときの不足が重なりました。

爪先の高さも、関節の角度から見積もりました(FK 表 fk_scan_motion_based_v2.csv の近い 40 点からの当てはめで、LOG361 と同じ方法)。1点ずつは揺れがありますが、持っている間の最高は、位置D がお手本 135〜137mm に対して 116.5mm、位置E がお手本 115〜120mm に対して 90.8mm でした。位置E は、お手本より 24〜29mm 低いところを通ったことになり、動画で縁すれすれだったことと合っています。

お手本と比べたスクリプト de_vs_demo.py

03_de_vs_demo.py.txt4,071字・68行・python
# 位置D・位置E の実機の回 (LOG363) を, 同じ位置のお手本 (D 5本・E 4本) と比べる
#  1) つかんだ姿勢 / 運ぶ間に肩がいちばん上がった所 (j1 の最小) / 離した姿勢
#  2) ブロックを持って肩を上げる指令の, 目標と次のチャンクの実測の差 (+ は目標より低いまま止まった)
#  3) 爪先の高さ (FK 表 fk_scan_motion_based_v2.csv の ee_y を (j1,j2,j3,j4) の近い 40 点で局所線形に当てはめる, LOG361 と同じ)
#  実機は中継点ごとに「送った目標 (target)」と「着いたあとの実測 (actual_after)」が残っているので, 肩の高さと爪先の高さは両方を出す
import os, json
import numpy as np, pandas as pd
ROOT = os.environ.get("R2R2R_DATA", "/Volumes/KIOXIA/r2r2r-data"); os.chdir(ROOT)
別のタブで開く保存

出力 out_de_vs_demo.txt

04_out_de_vs_demo.txt7,096字・119行・text

######## 位置D
== お手本 (閉じた瞬間 / 持っている間の j1 最小 / 開いた瞬間, 角度は j0..j5)
demo 193404  閉 [ -61.6  +62.5  +14.8   +1.5  -60.7  -32.4]  j1最小 +45.9 (j0 +19.3)  開 [ +43.8  +63.4   -3.3   +7.4  -61.7  -35.7]
            爪先の高さ mm  閉じた時   31.7  最高  135.3  1秒ごと    32    43    51    30    71   132    95    89
demo 193459  閉 [ -61.6  +62.4  +14.8   +1.1  -60.8  -31.7]  j1最小 +45.9 (j0 +20.5)  開 [ +43.7  +63.4   -3.3   +7.4  -62.0  -35.8]
            爪先の高さ mm  閉じた時   32.5  最高  135.6  1秒ごと    32    44    13    95   122    64   109    88
demo 193617  閉 [ -61.7  +62.4  +14.8   +1.1  -60.8  -31.7]  j1最小 +45.9 (j0 +20.8)  開 [ +43.8  +63.5   -3.3   +7.5  -61.7  -35.8]
別のタブで開く保存

位置C・D・E の3か所とも、1回以上ブロックが箱に入った

前の回の位置C と合わせて、AI に渡す角度を直前の目標にするやり方(--state-from-target)と中継点(--via-stride 3)で動かした回をまとめます。

位置お手本の本数動かした回箱に入った回入り方
C(LOG361)4本
7回(18:13〜18:32)
1回(18:16)
持ち上げて箱の上で離した。ほかの6回は、18:13・18:24・18:25 を途中で止め、18:26 は箱の位置で離したが動画がなく入ったかは分からず、18:30・18:32 はつかみ損ねて止めた
D5本1回(19:02)1回
離す直前に爪の先から抜け、箱の中に落ちた
E4本
2回(19:13・19:16)と、写り方の確認で止まった1回(19:14)
1回(19:16)
箱の手前の縁のすぐ上を通って、箱の上で離した
  • ねらい:位置C で入ったのが、その置き場所だけのたまたまかを確かめる
  • 変わったこと:モデル・学習・クライアントの設定は前の回のまま、ブロックの置き場所だけを D と E に変えた
  • 見込める効果:お手本のある3か所すべてで、1回以上箱に入った(入った回/動かした回は C 1/7・D 1/1・E 1/2。記録から数えた値)。置き場所によらず入る、という見込みが立ったので、次は1か所あたりの回数を増やして、入る割合を数えられる

ただ、入った3回のうち2回(D と E)はぎりぎりでした。今の割合のまま回数を増やすと、同じ理由で落ちたり縁に当たったりする回が出ると見ています(見込み)。

Pod を消す前に、サーバーのログを持ち帰った

位置E のあと、この回のテストを終えました。Pod を消すと中身がなくなるので、先にサーバーのログを Mac の KIOXIA へ持ち帰っています。

(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % mkdir -p /Volumes/KIOXIA/r2r2r-data/lab/Log13
scp -P <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP>:/workspace/logs/serve.log /Volumes/KIOXIA/r2r2r-data/lab/Log13/serve.log
serve.log                                     100% 2721     4.9KB/s   00:00    
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts %

そのあと、Runpod の画面で Stop → Terminate を押して、Pod を消しました。

項目値
GPUA40 × 1(Runpod)
料金
1時間 0.62ドル(約98円)
借りた時間
約1時間(Pod の画面の残高が 4.44ドル → 3.82ドル)
費用0.62ドル(約98円)
そのうちモデルを送る待ち
約30分(6,340,573,809 バイト、1秒あたり約 3.9MB)
サーバーの起動モデルの読み込み 5.02 秒
実機の回
4回(位置D 1回、位置E 3回。うち1回は写り方の確認で止まった)

次は C・D・E を何回かずつ動かして、箱に入る割合を数える

ここまでで、どの置き場所でも1回は入ることが分かりました。次は、同じやり方のまま、C・D・E を何回かずつ動かして、入る割合を数えます。そのときに見ておくことは次の3つです。

  • 運ぶ間の肩の高さ。位置E のように、お手本より 4° 低いと箱の縁すれすれになる。ブロックを持っている間の肩の止まり方(目標より約 1〜3° 低い)が、毎回どのくらい出るか
  • つかみの深さ。位置D のように爪の先でつかむと、回転を止めたときに抜ける。置いた位置のずれ(1cm 弱)と関係があるか
  • 位置E のつかみ損ね。ブロックが爪の陰に隠れる場所で、置いた位置のずれがどこまで許されるか

Pod を借りるたびにモデル 6.3GB を送り直していて、借りた時間の半分がその待ちでした。何回も試すなら、モデルを Pod 側に置いたままにしておける方法(Runpod のネットワークボリュームなど)を、先に調べておくのがよさそうです(見込み)。

実機の回すべてのチャンクの記録

この回に動かした4回(位置D 1回、位置E 3回)の log.jsonl を、1行 = 1チャンクの表にしました。state は AI に渡した角度、measured はそのとき測った角度、target はそのチャンクの最後に送った目標です。

表を作ったスクリプト run_chunk_tables.py

"""LOG363: 2026-10-24 の π0.5 実機の回 (位置D・位置E) を, チャンクごとの表にする (読むだけ).
runs/pi05_policy/pi05_20261024_*/log.jsonl を読み, 1 行 = 1 チャンクで
推論時間 / 使ったステップ / モデルに渡した state / 送った目標 / グリッパー /
送ったか / 着いたか / state の出どころ / 中継点の数 を出す.
"""
import json, glob, os
ROOT = os.environ.get("PI05_RUNS", "/Volumes/KIOXIA/r2r2r-data/runs/pi05_policy")
def f6(v):
    return "[" + " ".join(f"{x:+6.1f}" for x in v[:6]) + "]" if v else "-"
for d in sorted(glob.glob(ROOT + "/pi05_20261024_*")):
    L = [json.loads(l) for l in open(d + "/log.jsonl")]
    fr = [l for l in L if l.get("type") == "framing"]
    ch = [l for l in L if "chunk" in l]
    print(f"== {os.path.basename(d)}  チャンク {len(ch)}")
    for f in fr:
        print(f"  framing dy={f['dy']} dx={f['dx']} response={f['response']} -> {f['verdict']}")
    for c in ch:
        src = c.get("state_source", "measured")
        vias = len(c.get("vias") or [])
        print(f"  {c['chunk']:02d} {c['latency_s']*1000:6.0f}ms used={c.get('used_step')} "
              f"state={f6(c['state'])} grip_state={c['state'][6]:.0f} src={src:8s} target={f6(c.get('target'))} "
              f"grip={c.get('grip_cmd')} exec={c.get('executed')} arrived={c.get('arrived')} vias={vias}")
        if src == "target" and c.get("measured_state"):
            print(f"       measured={f6(c['measured_state'])}")

出力 out_run_chunk_tables.txt

05_out_run_chunk_tables.txt9,307字・79行・text
== pi05_20261024_190239  チャンク 17
  framing dy=0.26 dx=1.41 response=0.574 -> OK
  00  56893ms used=9 state=[  -0.1   +1.2   +1.5   +0.3   -0.3  -29.7] grip_state=0 src=measured target=[ -20.0  +19.8   +7.1   +1.0  -26.2  -31.4] grip=None exec=True arrived=True vias=4
  01    815ms used=9 state=[ -20.0  +19.8   +7.1   +1.0  -26.2  -31.4] grip_state=0 src=target   target=[ -41.4  +40.4  +14.4   +1.4  -54.5  -31.7] grip=None exec=True arrived=True vias=4
       measured=[ -20.2  +20.8   +7.9   +0.7  -26.2  -31.1]
  02   2178ms used=9 state=[ -41.4  +40.4  +14.4   +1.4  -54.5  -31.7] grip_state=0 src=target   target=[ -57.5  +54.2  +20.8   +2.0  -74.2  -31.8] grip=None exec=True arrived=True vias=4
       measured=[ -41.7  +41.4  +14.9   +1.8  -55.1  -31.4]
  03   1036ms used=9 state=[ -57.5  +54.2  +20.8   +2.0  -74.2  -31.8] grip_state=0 src=target   target=[ -59.0  +58.2  +18.3   +1.7  -67.1  -31.9] grip=None exec=True arrived=True vias=4
別のタブで開く保存

図解の数字を作ったスクリプト figure_data.py

図解に入れた、ブロックを持っている間の土台 j0 と肩 j1 の組を作ったスクリプトと、その出力です。

# 図解 LOG363_shoulder_path.html に入れる数字を作る (LOG363)
#  ブロックを持っている間の「土台 j0 (箱へ回った角度)」と「肩 j1 (小さいほど腕が上がっている)」の組
#  お手本: 位置D 193404 / 位置E 194818 (10Hz の全ステップ)
#  実機: 位置D 190239 / 位置E 191603 の中継点ごとの目標 (target) と着いたあとの実測 (actual_after)
import os, json
import numpy as np
ROOT = os.environ.get("R2R2R_DATA", "/Volumes/KIOXIA/r2r2r-data"); os.chdir(ROOT)
RAW = "datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes"
out = {}
for pos, demo, run in [("D", "193404", "20261024_190239"), ("E", "194818", "20261024_191603")]:
    S = np.load(f"{RAW}/run_0001_20261019_{demo}/episode.npz")["state"]
    g = S[:, 6]; c = int(np.argmax(g > 0.5)); o = c + int(np.argmax(g[c:] < 0.5))
    dm = [[round(float(s[0]), 1), round(float(s[1]), 1)] for s in S[c:o + 1]]
    L = [json.loads(l) for l in open(f"runs/pi05_policy/pi05_{run}/log.jsonl")]
    L = [x for x in L if "chunk" in x and x["state"][6] == 1]
    tg, ac = [], []
    for x in L:
        for v in x.get("vias") or []:
            tg.append([round(v["target"][0], 1), round(v["target"][1], 1)])
            ac.append([round(v["actual_after"][0], 1), round(v["actual_after"][1], 1)])
    out[pos] = {"demo": dm, "target": tg, "actual": ac}
print(json.dumps(out, ensure_ascii=False))

出力 out_figure_data.json

{"D": {"demo": [[-61.6, 62.5], [-61.6, 62.5], [-61.6, 62.5], [-61.6, 62.5], [-61.6, 62.5], [-61.6, 62.5], [-61.6, 62.5], [-61.6, 62.4], [-61.5, 61.8], [-61.3, 61.3], [-61.2, 60.7], [-61.1, 60.1], [-61.0, 59.6], [-60.8, 59.0], [-60.7, 58.5], [-60.6, 57.9], [-60.4, 57.4], [-60.3, 56.8], [-60.3, 56.7], [-60.3, 56.7], [-60.3, 56.7], [-59.8, 56.6], [-57.1, 56.2], [-54.3, 55.8], [-51.6, 55.4], [-48.9, 55.0], [-46.1, 54.6], [-43.4, 54.2], [-40.6, 53.9], [-37.9, 53.5], [-35.1, 53.1], [-32.4, 52.7], [-29.3, 52.2], [-26.1, 51.8], [-22.9, 51.3], [-19.7, 50.8], [-16.6, 50.4], [-13.6, 50.0], [-10.6, 49.6], [-7.8, 49.1], [-5.0, 48.6], [-2.2, 48.1], [0.7, 47.7], [3.7, 47.3], [6.6, 46.9], [9.7, 46.6], [12.9, 46.3], [16.1, 46.0], [19.3, 45.9], [22.4, 45.9], [25.5, 45.9], [28.6, 45.9], [31.5, 45.9], [34.5, 45.9], [37.2, 45.9], [39.2, 45.9], [41.2, 45.9], [43.2, 45.9], [43.3, 46.9], [43.3, 48.5], [43.4, 50.2], [43.4, 51.9], [43.5, 53.5], [43.5, 55.2], [43.6, 56.9], [43.6, 58.6], [43.7, 60.2], [43.7, 61.9], [43.8, 63.4], [43.8, 63.4], [43.8, 63.4], [43.8, 63.4]], "target": [[-59.4, 61.0], [-59.1, 60.1], [-59.4, 58.6], [-59.5, 58.4], [-59.6, 57.4], [-59.6, 56.7], [-59.1, 56.6], [-57.7, 56.4], [-49.7, 55.6], [-41.1, 54.5], [-32.6, 53.5], [-29.8, 53.2], [-20.6, 51.7], [-12.1, 50.4], [-3.3, 49.2], [-1.2, 49.3], [7.5, 48.0], [16.9, 47.0], [26.7, 46.5], [28.6, 46.6], [37.1, 46.1], [42.0, 46.6], [42.6, 51.0], [42.4, 52.6], [42.6, 57.8], [42.6, 61.9], [43.1, 63.0], [43.1, 63.0]], "actual": [[-59.0, 62.6], [-59.0, 62.0], [-59.1, 60.8], [-59.1, 60.8], [-59.1, 59.5], [-59.1, 59.2], [-59.1, 59.2], [-57.7, 58.9], [-50.1, 57.7], [-41.3, 56.9], [-32.9, 55.5], [-30.2, 55.5], [-20.8, 54.1], [-12.3, 52.6], [-3.5, 51.7], [-1.3, 51.7], [7.3, 50.5], [16.7, 49.7], [26.4, 48.8], [28.6, 48.8], [37.0, 48.8], [42.1, 48.8], [42.5, 52.2], [42.5, 53.8], [42.5, 58.9], [42.5, 63.1], [42.5, 64.1], [42.5, 64.1]]}, "E": {"demo": [[13.2, 60.0], [13.2, 60.0], [12.9, 60.0], [12.9, 60.0], [12.7, 60.0], [12.7, 58.9], [12.8, 57.6], [12.8, 56.4], [12.8, 55.1], [12.8, 53.8], [12.8, 52.6], [12.8, 51.3], [12.8, 50.0], [12.8, 48.8], [12.8, 47.5], [12.8, 46.3], [14.8, 46.6], [17.1, 46.9], [19.4, 47.2], [21.7, 47.5], [23.9, 47.8], [26.2, 48.1], [28.5, 48.4], [30.8, 48.8], [33.0, 49.1], [35.3, 49.4], [37.5, 49.7], [39.6, 50.2], [41.7, 50.7], [43.4, 51.1], [43.4, 52.6], [43.3, 54.1], [43.3, 55.6], [43.2, 57.1], [43.2, 58.6], [43.2, 60.1], [43.1, 61.5], [43.1, 63.0], [43.0, 64.5], [43.0, 66.0], [43.0, 66.9], [43.0, 66.9], [42.9, 66.9], [42.9, 66.9]], "target": [[12.0, 59.3], [12.1, 56.0], [12.0, 52.5], [12.1, 51.5], [11.8, 48.3], [16.7, 48.7], [23.3, 49.3], [25.0, 49.5], [31.5, 50.6], [38.4, 52.6], [42.4, 55.0], [42.7, 56.4], [42.8, 60.9], [42.9, 64.5], [42.9, 65.8], [42.9, 65.8]], "actual": [[11.9, 60.8], [11.9, 57.6], [11.9, 54.5], [11.9, 53.6], [11.9, 50.3], [16.6, 50.3], [23.3, 50.3], [25.0, 50.5], [31.6, 51.7], [38.3, 53.6], [42.3, 56.2], [42.4, 57.6], [43.0, 61.8], [43.0, 65.2], [43.0, 66.3], [43.0, 66.3]]}}

スクリプトは、Mac 連携の作業環境から、データの場所を環境変数で渡して動かしました(Mac で動かすときは、何も渡さなければ /Volumes/KIOXIA/r2r2r-data を読みます)。

export R2R2R_DATA=$HOME/mnt/KIOXIA/r2r2r-data PI05_RUNS=$HOME/mnt/KIOXIA/r2r2r-data/runs/pi05_policy
python3 run_chunk_tables.py > out_run_chunk_tables.txt
python3 de_vs_demo.py > out_de_vs_demo.txt
python3 block_position_mm.py > out_block_position_mm.txt
python3 figure_data.py > out_figure_data.json