diff --git a/lab_start.sh b/lab_start.sh index 5fe03d2..8c18032 100755 --- a/lab_start.sh +++ b/lab_start.sh @@ -23,7 +23,8 @@ # 取る場所・設定・assets・サーバーの起動・つなぎ直しの確かめが選んだモデルに合う. 安全範囲 (学習データ ±10°. # run_policy_client.py --raw-dir) は, deep1・deep1b は deep_even10_v1 の raw_episodes (依頼 135), ほかは trim2 の # raw_episodes (corr_v3・corr_v4 と合わせても範囲はほぼ同じで, trim2 の方が狭い. corr_v5 は 11-06 の 184650 の j2 が -# trim2 の上限 +31.4° を最大 11° 超える). 位置合わせ (camera_align.py) のお手本はどれも trim2 の raw_episodes +# trim2 の上限 +31.4° を最大 11° 超える). 位置合わせ (camera_align.py) のお手本と, 実機推論の写り方の確かめの基準画像 +# (run_policy_client.py --framing-raw-dir. 依頼 137) はどれも trim2 の raw_episodes # つなぎ直しで, 残っている Pod のサーバーが別のモデルで動いていれば, そのことを出して止まる # # 1. Pod を作る (A40×1・Runpod Pytorch 2.8.0・コンテナのディスク 200GB・22/tcp を外へ出す) @@ -113,6 +114,7 @@ RAW=/Volumes/KIOXIA/r2r2r-data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_ RAW_RANGE=/Volumes/KIOXIA/r2r2r-data/datasets/$MODEL_RANGE_DATA/raw_episodes # 安全範囲を作る学習データ (lab_lib.sh の lab_model_def. 依頼 135) [ -d "$RAW_RANGE" ] || { echo "[ERROR] 安全範囲を作る学習データがありません: $RAW_RANGE" >&2; exit 1; } echo "[RANGE ] 安全範囲 (学習データ ±10°) は $RAW_RANGE から (--model $MODEL)" +echo "[FRAME ] 写り方の確かめの基準画像は $RAW の最初のエピソードから (位置合わせと同じ trim2. 依頼 137)" lab_ssh_opts HF_REPO=$MODEL_REPO # --model で選んだモデル (lab_lib.sh の lab_model_def) @@ -452,12 +454,12 @@ while :; do stage_done stage "8. 実機推論 ($round 回目)" python3 pi05/run_policy_client.py --raw-dir "$RAW_RANGE" --steps-per-chunk 10 --via-stride 3 \ - --settle-retries 0 --state-from-target --execute --ignore-framing --model "$MODEL_TAG" + --settle-retries 0 --state-from-target --execute --ignore-framing --model "$MODEL_TAG" --framing-raw-dir "$RAW" rc=$? else stage "8. 推論のクライアント (ドライラン・--dry-keys. アームには触らない)" python3 pi05/run_policy_client.py --raw-dir "$RAW_RANGE" --steps-per-chunk 10 --via-stride 3 \ - --settle-retries 0 --state-from-target --dry-keys --ignore-framing --model "$MODEL_TAG" + --settle-retries 0 --state-from-target --dry-keys --ignore-framing --model "$MODEL_TAG" --framing-raw-dir "$RAW" rc=$? fi echo "[CLIENT] run_policy_client.py が終わった (終了コード $rc. モデル $MODEL_TAG)" diff --git a/pi05/run_policy_client.py b/pi05/run_policy_client.py index 23fbd21..8ac31ec 100644 --- a/pi05/run_policy_client.py +++ b/pi05/run_policy_client.py @@ -1082,6 +1082,11 @@ def apply_wait_args(args): f"{GRIP_OPEN_DAEMON_WAIT:.1f}s) / 止まるのを待つ目安 {SETTLE_WAIT_SEC:.1f}s") +def framing_raw_dir(args) -> Path: + """写り方の確かめの基準画像を取るお手本. --framing-raw-dir がなければ --raw-dir (依頼 137).""" + return Path(args.framing_raw_dir or args.raw_dir) + + def main(): ap = argparse.ArgumentParser() ap.add_argument("--host", default="localhost") @@ -1093,6 +1098,9 @@ def main(): ap.add_argument("--max-move-deg", type=float, default=20.0, help="1 回の移動で各関節が動ける上限") ap.add_argument("--raw-dir", required=True, help="安全範囲の算出に使う学習エピソード (必須. 学習に使った raw_episodes を指定する)") + ap.add_argument("--framing-raw-dir", default=None, + help="写り方の確かめの基準画像を取るお手本 (最初のエピソードの 1 枚目). " + "未指定なら --raw-dir から (依頼 137. lab_start.sh は位置合わせと同じ trim2 を渡す)") ap.add_argument("--prompt", default=TASK) ap.add_argument("--model", default=None, help="サーバーが使っているモデルの名前 (記録に残すだけ. lab_start.sh が --model に合わせて渡す. 依頼 52)") @@ -1147,6 +1155,7 @@ def main(): print(f"[MODEL ] {args.model or '(指定なし)'}") # どのモデルで推論したかを log.jsonl の頭に残す ({run_stem}.json には args.model で残る. 依頼 52) log_f.write(json.dumps({"type": "model", "model": args.model, "raw_dir": str(args.raw_dir), + "framing_raw_dir": str(framing_raw_dir(args)), "host": args.host, "port": args.port, "t": time.time()}, ensure_ascii=False) + "\n") log_f.flush() @@ -1183,7 +1192,7 @@ def main(): # (082645 は dy=+37.5 のまま実機を動かした. その 1 分前のドライラン 082512 は dy=-7.8). # アームは HOME に置いた状態で, 1 枚目を お手本の 1 枚目 (同じく HOME) と比べる. # **この確認が通るまで daemon にも触らない** (アームには何も送らない). - ref_bgr, ref_path = load_reference(Path(args.raw_dir)) + ref_bgr, ref_path = load_reference(framing_raw_dir(args)) bgr0 = None for _ in range(10): _rgb0, bgr0 = grab_rgb(cap) diff --git a/pi05/test_framing_raw_dir.py b/pi05/test_framing_raw_dir.py new file mode 100644 index 0000000..82121a8 --- /dev/null +++ b/pi05/test_framing_raw_dir.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""test_framing_raw_dir.py — run_policy_client.py の写り方の確かめの基準画像の取り先 (依頼 137). + + - --framing-raw-dir を付けると, 基準画像 (load_reference) はそこから取り, 安全範囲 (load_envelope) は --raw-dir のまま + - 付けなければ今までどおり --raw-dir から取る + - log.jsonl の頭 (type=model) に framing_raw_dir が残る + +アーム・カメラ・daemon・サーバーには触らない. load_reference を偽物にして, そこで止める. + + python3 pi05/test_framing_raw_dir.py +""" +import json +import sys +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +import numpy as np + +_HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(_HERE)) + +import run_policy_client as rpc # noqa: E402 + + +class _Stop(Exception): + pass + + +class _FakeCap: + def isOpened(self): + return True + + def read(self): + return True, np.zeros((4, 4, 3), np.uint8) + + def release(self): + pass + + +def _run(argv): + """main を load_reference まで流す. 戻り値: (load_envelope の引数, load_reference の引数, log.jsonl の頭).""" + seen = {} + + def fake_envelope(p): + seen["envelope"] = Path(p) + return np.full(6, -10.0), np.full(6, 10.0), 1 + + def fake_reference(p, *a, **k): + seen["reference"] = Path(p) + raise _Stop() + + with tempfile.TemporaryDirectory() as td: + td = Path(td) + with mock.patch.object(rpc, "DATA_ROOT", td), \ + mock.patch.object(rpc, "LOG_ROOT", td / "logs"), \ + mock.patch.object(rpc, "load_envelope", fake_envelope), \ + mock.patch.object(rpc, "load_reference", fake_reference), \ + mock.patch.object(rpc.camera_resolver, "resolve_cameras", lambda **k: {"top": 0}), \ + mock.patch.object(rpc.cv2, "VideoCapture", lambda *a, **k: _FakeCap()), \ + mock.patch.object(sys, "argv", ["run_policy_client.py"] + argv), \ + mock.patch("builtins.print"): + try: + rpc.main() + except _Stop: + pass + logs = sorted((td / "logs").glob("pi05_*/log.jsonl")) + head = json.loads(logs[0].read_text().splitlines()[0]) + return seen["envelope"], seen["reference"], head + + +class TestFramingRawDir(unittest.TestCase): + def test_framing_raw_dir_given(self): + env, ref, head = _run(["--raw-dir", "/x/deep_even10_v1/raw_episodes", + "--framing-raw-dir", "/x/trim2/raw_episodes", "--dry-keys", "--model", "deep1"]) + self.assertEqual(env, Path("/x/deep_even10_v1/raw_episodes")) # 安全範囲は --raw-dir のまま + self.assertEqual(ref, Path("/x/trim2/raw_episodes")) # 基準画像は --framing-raw-dir + self.assertEqual(head["type"], "model") + self.assertEqual(head["raw_dir"], "/x/deep_even10_v1/raw_episodes") + self.assertEqual(head["framing_raw_dir"], "/x/trim2/raw_episodes") + + def test_framing_raw_dir_default(self): + env, ref, head = _run(["--raw-dir", "/x/trim2/raw_episodes", "--dry-keys", "--model", "trim"]) + self.assertEqual(env, Path("/x/trim2/raw_episodes")) + self.assertEqual(ref, Path("/x/trim2/raw_episodes")) # 付けなければ今までどおり --raw-dir + self.assertEqual(head["framing_raw_dir"], "/x/trim2/raw_episodes") + + +if __name__ == "__main__": + unittest.main() diff --git a/pi05/test_rewind_correct.py b/pi05/test_rewind_correct.py index 353003c..0df4063 100644 --- a/pi05/test_rewind_correct.py +++ b/pi05/test_rewind_correct.py @@ -597,7 +597,7 @@ class TestClientFlow(unittest.TestCase): self.assertEqual(w_old.sends, w_new.sends, argv) self.assertEqual(w_old.grips, w_new.grips, argv) self.assertEqual(len(l_old), len(l_new)) - new_keys = ("t_exec_start", "t_exec_end", "sent_targets") + new_keys = ("t_exec_start", "t_exec_end", "sent_targets", "framing_raw_dir") # framing_raw_dir は依頼 137 # HEAD が依頼 25 より前なら new_keys は新しい方にだけある. 依頼 25 以降なら両方にある (両方から外す) drop = ("t", "latency_s", "reference") + new_keys so, sn = strip(l_old, drop=drop), strip(l_new, drop=drop) diff --git a/test_lab_start.sh b/test_lab_start.sh index bcb8528..c98ed67 100755 --- a/test_lab_start.sh +++ b/test_lab_start.sh @@ -342,6 +342,7 @@ check "3999 を取り, trim の設定でサーバーを起動" 'has ssh.log "hf check "3f は今までと同じ (corr の設定は足さない)" 'has ssh.log "$(. "$HERE/lab_lib.sh"; lab_cmd_addon)" && ! has ssh.log "add_trim_corr_config.py"' check "クライアントに --model trim を渡す・安全範囲は trim2" 'has py.log "--model trim (PixelstoMotors/pi05-mecharm-trim-lora-v1)" && has py.log "datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes "' check "trim の安全範囲 (run_policy_client.py --raw-dir) は trim2 のまま・deep_even10_v1 は使わない (依頼 135)" 'has py.log "run_policy_client.py --raw-dir $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes --steps-per-chunk" && ! has py.log "deep_even10_v1" && has out.txt "[RANGE ] 安全範囲 (学習データ ±10°) は $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes から (--model trim)"' +check "trim の写り方の基準画像 (--framing-raw-dir) も今までどおり trim2 (依頼 137)" 'has py.log "--ignore-framing --model trim (PixelstoMotors/pi05-mecharm-trim-lora-v1) --framing-raw-dir $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes" && has out.txt "[FRAME ] 写り方の確かめの基準画像は $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes の最初のエピソードから"' check "記録 lab_pod.json に model=trim" '[ "$(jq -r .model "$T/data/lab/LogT/pod/lab_pod.json")" = trim ]' check "lab_start_*.txt にモデルが残る" 'grep -qF "[MODEL ] --model trim" "$T/data/lab/LogT"/lab_start_*.txt && grep -qF "モデル trim (PixelstoMotors/pi05-mecharm-trim-lora-v1)" "$T/data/lab/LogT"/lab_start_*.txt' rm -rf "$T" @@ -421,7 +422,8 @@ check "deep1 のモデルを /workspace/ckpt/deep_v1_2999 に取る (ほかの check "3f で学習と同じ deep の設定 (3000 ステップ・--variant v1 = decay_steps 5000) を足し, モデルの assets (測り直した正規化の値) を写す" 'has ssh.log "add_deep_config.py /workspace/openpi --steps 3000 --variant v1 &&" && ! has ssh.log "add_trim_corr_config.py" && has ssh.log "mkdir -p assets/pi05_mecharm_deep_even10_lora/local && cp -r /workspace/ckpt/deep_v1_2999/assets/local/mecharm_redblock_box_deep_even10_v1 assets/pi05_mecharm_deep_even10_lora/local/"' check "deep1 の設定とフォルダでサーバーを起動" 'has ssh.log "--policy.config=pi05_mecharm_deep_even10_lora --policy.dir=/workspace/ckpt/deep_v1_2999 2>&1"' check "クライアントに --model deep1 を渡す" 'has py.log "--model deep1 (PixelstoMotors/pi05-mecharm-deep-even10-lora-v1)"' -check "deep1 の安全範囲 (run_policy_client.py --raw-dir) は deep_even10_v1 の raw_episodes・trim2 は使わない (依頼 135)" 'has py.log "run_policy_client.py --raw-dir $T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes --steps-per-chunk" && ! has py.log "teach_v1_trim2" && has out.txt "[RANGE ] 安全範囲 (学習データ ±10°) は $T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes から (--model deep1)"' +check "deep1 の安全範囲 (run_policy_client.py --raw-dir) は deep_even10_v1 の raw_episodes・trim2 は使わない (依頼 135)" 'has py.log "run_policy_client.py --raw-dir $T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes --steps-per-chunk" && ! has py.log "--raw-dir $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes" && has out.txt "[RANGE ] 安全範囲 (学習データ ±10°) は $T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes から (--model deep1)"' +check "deep1 の写り方の基準画像 (--framing-raw-dir) は trim2・deep_even10_v1 ではない (依頼 137)" 'has py.log "--ignore-framing --model deep1 (PixelstoMotors/pi05-mecharm-deep-even10-lora-v1) --framing-raw-dir $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes" && ! has py.log "--framing-raw-dir $T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes" && has out.txt "[FRAME ] 写り方の確かめの基準画像は $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes の最初のエピソードから" && has out.txt "[RANGE ] 安全範囲 (学習データ ±10°) は $T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes から (--model deep1)"' check "記録に model=deep1" '[ "$(jq -r .model "$T/data/lab/LogT/pod/lab_pod.json")" = deep1 ]' rm -rf "$T" setup; rmdir "$T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes"; run --model deep1 @@ -429,6 +431,7 @@ check "deep1 で deep_even10_v1 の raw_episodes がない → API を呼ばず rm -rf "$T" setup; rmdir "$T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes"; run --model corr3 check "corr3 は deep_even10_v1 がなくても trim2 で進む (依頼 135)" '[ $RC = 0 ] && has py.log "run_policy_client.py --raw-dir $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes --steps-per-chunk"' +check "corr3 の写り方の基準画像も今までどおり trim2 (依頼 137)" 'has py.log "--model corr3 (PixelstoMotors/pi05-mecharm-trim-corr-lora-v3) --framing-raw-dir $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes" && has out.txt "[FRAME ] 写り方の確かめの基準画像は $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes の最初のエピソードから"' rm -rf "$T" setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; serve_dir /workspace/ckpt/corr_v3_1499; run --model deep1 check "corr3 のサーバーが動いている Pod に --model deep1 → 止まり, --model corr3 を案内" '[ $RC = 1 ] && has out.txt "別のモデル (--model corr3)で動いています" && ! has ssh.log "tmux new" && ! has ssh.log "pkill"' @@ -442,6 +445,7 @@ check "deep1b のモデルを /workspace/ckpt/deep_b_v1_2999 に取る (deep1 check "3f で add_deep_config.py --steps 3000 --variant b を足し, モデルの assets を pi05_mecharm_deep_even10_b_lora に写す" 'has ssh.log "add_deep_config.py /workspace/openpi --steps 3000 --variant b &&" && ! has ssh.log "--variant v1" && has ssh.log "mkdir -p assets/pi05_mecharm_deep_even10_b_lora/local && cp -r /workspace/ckpt/deep_b_v1_2999/assets/local/mecharm_redblock_box_deep_even10_v1 assets/pi05_mecharm_deep_even10_b_lora/local/"' check "deep1b の設定とフォルダでサーバーを起動" 'has ssh.log "--policy.config=pi05_mecharm_deep_even10_b_lora --policy.dir=/workspace/ckpt/deep_b_v1_2999 2>&1"' check "deep1b の安全範囲も deep_even10_v1 の raw_episodes" 'has py.log "--model deep1b (PixelstoMotors/pi05-mecharm-deep-even10-b-lora-v1)" && has py.log "run_policy_client.py --raw-dir $T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes --steps-per-chunk"' +check "deep1b の写り方の基準画像は trim2・安全範囲は deep_even10_v1 (依頼 137)" 'has py.log "--model deep1b (PixelstoMotors/pi05-mecharm-deep-even10-b-lora-v1) --framing-raw-dir $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes" && ! has py.log "--framing-raw-dir $T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes" && ! has py.log "--raw-dir $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes" && has out.txt "[FRAME ] 写り方の確かめの基準画像は $T/data/datasets/mecharm_redblock_box_teach_v1_trim2/raw_episodes の最初のエピソードから" && has out.txt "[RANGE ] 安全範囲 (学習データ ±10°) は $T/data/datasets/mecharm_redblock_box_deep_even10_v1/raw_episodes から (--model deep1b)"' rm -rf "$T" setup; state $ID_OLD; pod 200 "$RUNNING"; probe 1 1 1 1 1; serve_dir /workspace/ckpt/deep_v1_2999; run --model deep1b check "deep1 のサーバーが動いている Pod に --model deep1b → 止まり, --model deep1 を案内" '[ $RC = 1 ] && has out.txt "別のモデル (--model deep1)で動いています" && ! has ssh.log "tmux new" && ! has ssh.log "pkill"'