"""依頼113 G: g_timing.csv と g_grasp_metrics.csv を 1 本 1 行にまとめ (g_table.csv), README 用の表を出す (標準出力). 深さの物差しは「爪の向きに沿うはみ出し」(g_images.measure_axis. axis_protrude_px). E の値 (縦の差) とは 0〜7px 違うので, E の 16 件も同じ物差しで測り直した値 (dataset=E_failures / E_successes) と並べる. scripted_v1 はカメラが少し引いていて爪先の印の間 (開き) が 122〜127px (ほかは 134〜140px) なので, px を「開いた印の間 137px」にそろえた値 (scaled) も出す. 「手」: 閉じる前のコマに人の手が写る本 (画で確かめた. HAND). 肌を赤として拾うと値が 100px を超えるので, 閉じる前の値で 100px を超えるものは使わない (表では「—」). """ import csv from pathlib import Path import numpy as np OUT = Path(__file__).resolve().parent HAND = {"run_0001_20261019_191347", "run_0001_20261019_191441", # scripted_v1: 182552・185536・175444 は直前まで, 183310・184725・190925 は 1.5 秒前のコマだけ手が写る "run_0001_20261015_182552", "run_0001_20261015_183310", "run_0001_20261015_185536", "run_0001_20261016_175444", "run_0001_20261016_184725", "run_0001_20261016_190925"} SUCCESS_MAX_AFTER = None # E の成功の閉じたあとの最大 (下で計算) def f(v): try: return float(v) except (TypeError, ValueError): return None def main(): tim = {(r["dataset"], r["run"]): r for r in csv.DictReader(open(OUT / "g_timing.csv"))} gm = list(csv.DictReader(open(OUT / "g_grasp_metrics.csv"))) e_succ = [f(r["after_axis_protrude_px"]) for r in gm if r["dataset"] == "E_successes"] e_shallow = [f(r["after_axis_protrude_px"]) for r in gm if r["dataset"] == "E_failures" and f(r["after_axis_marker_gap_px"]) > 90 and f(r["after_axis_protrude_px"]) > max(e_succ) + 10] line = max(e_succ) + 10 rows = [] for g in gm: t = tim.get((g["dataset"], g["run"]), {}) gap_open = f(g.get("before_axis_marker_gap_px")) or 137.0 sc = 137.0 / gap_open if g["dataset"] == "scripted_v1" else 1.0 o = {"dataset": g["dataset"], "run": g["run"], "kind": g["kind"], "hand_in_frame": g["run"] in HAND, "pre1p5_px": g.get("pre1p5_axis_protrude_px"), "pre0p5_px": g.get("pre0p5_axis_protrude_px"), "before_px": g.get("before_axis_protrude_px"), "label_frame_px": g.get("ep_label_axis_protrude_px"), "after_px": g.get("after_axis_protrude_px"), "after_gap_px": g.get("after_axis_marker_gap_px"), "scale_to_137": round(sc, 3), "E_before_px": g.get("before_protrude_px"), "E_after_px": g.get("after_protrude_px")} a = f(o["after_px"]) o["after_px_scaled"] = None if a is None else round(a * sc, 1) o["after_mm_approx"] = None if a is None else round(a * sc / 2.6, 1) o["shallow_like_failures"] = (a is not None and a * sc > line and (f(o["after_gap_px"]) or 0) * sc > 90) for k in ("label_mode", "t_label_minus_cmd_s", "t_cmd_minus_stop_s", "stop_gap_before_s", "speed_pre0p3_deg_s", "move_after_cmd_1p5s_deg", "d_j1_pre", "d_j2_pre", "d_j4_pre", "d_reach_pre_mm", "d_reach_last0p5_mm", "d_h_pre_mm", "d_h_last0p5_mm", "grid_steps_pm1p5", "ep_steps_pm1p5", "t_label_minus_grid_first1_s", "act-1_d_reach_mm", "act-1_max_joint_deg", "correct_steps_before_label", "correct_d_reach_mm", "correct_d_h_mm", "correct_d_joints"): o[k] = t.get(k) rows.append(o) with open(OUT / "g_table.csv", "w", newline="") as fo: w = csv.DictWriter(fo, fieldnames=list(rows[0])); w.writeheader(); w.writerows(rows) print(f"E 成功の閉じたあと最大 {max(e_succ)} px → 浅いとみなす線 {line:.1f} px / E 浅い失敗 {sorted(e_shallow)}") def ok(v): v = f(v) return "—" if v is None or v > 100 else v def short(r): return r.replace("run_0001_", "").replace("pi05_", "") print("\n| 本 | 種類 | 閉じたモード | 命令−止まり 秒 | 閉じる前 0.3 秒 °/s | 前 1.5 秒 j1/j2/j4 ° | 前 1.5 秒 伸び/高さ mm | " "最後 0.5 秒 伸び/高さ mm | 1.5 秒前 px | 直前 px | ラベルのコマ px | 1.0 秒後 px | 浅い |") print("|" + "---|" * 13) for o in rows: if o["dataset"] != "corr_v5_main": continue hand = "(手)" if o["hand_in_frame"] else "" print(f"| {short(o['run'])} | {'補正入り' if o['kind'] == 'corr' else '教えた'} | {o['label_mode']} | " f"{o['t_cmd_minus_stop_s']} | {o['speed_pre0p3_deg_s']} | {o['d_j1_pre']}/{o['d_j2_pre']}/{o['d_j4_pre']} | " f"{o['d_reach_pre_mm']}/{o['d_h_pre_mm']} | {o['d_reach_last0p5_mm']}/{o['d_h_last0p5_mm']} | " f"{ok(o['pre1p5_px'])} | {ok(o['before_px'])}{hand} | " f"{ok(o['label_frame_px'])} | {o['after_px']} | {'**浅い**' if o['shallow_like_failures'] else ''} |") print("\n群ごと (閉じたあと px. 手の本も閉じたあとは数える)") groups = {"教えた 19": lambda o: o["dataset"] == "corr_v5_main" and o["kind"] == "teach", "教えた うち試す置き場所 (194818〜195120) 4": lambda o: o["dataset"] == "corr_v5_main" and o["run"][-6:] in ("194818", "194913", "195023", "195120"), "補正入り 11": lambda o: o["dataset"] == "corr_v5_main" and o["kind"] == "corr", "scripted_v1 20 (137px にそろえた)": lambda o: o["dataset"] == "scripted_v1", "E 成功 4": lambda o: o["dataset"] == "E_successes", "E 失敗 12": lambda o: o["dataset"] == "E_failures"} for name, fn in groups.items(): sel = [o for o in rows if fn(o)] af = [o["after_px_scaled"] for o in sel if o["after_px_scaled"] is not None] bf = [f(o["before_px"]) * o["scale_to_137"] for o in sel if f(o["before_px"]) is not None and f(o["before_px"]) <= 100] n_sh = sum(o["shallow_like_failures"] for o in sel) print(f"- {name}: 直前 {min(bf):.0f}〜{max(bf):.0f} (中央値 {np.median(bf):.0f}) / 1.0 秒後 {min(af):.0f}〜{max(af):.0f} " f"(中央値 {np.median(af):.0f}) / 浅い (> {line:.0f}px) {n_sh} 本: " + ", ".join(short(o["run"]) for o in sel if o["shallow_like_failures"])) if __name__ == "__main__": main()