feat(tracking): 汇总 v0.1.3 回放与 ACT 准备进度

新增专家轨迹诊断、原视频估计相机入口和状态参考ACT数据门禁/训练链路;更新版本与进度文档。

验证:120项CPU/USD回归和4步synthetic CPU smoke通过;1333帧默认GUI历史回放PASS。原视频相机完整回放超时124,策略GPU E2E未执行,完整pre-commit工具缺失。

兼容性:HDF5、Cartpole、USD和控制阈值保持不变。本提交为实验进度快照,不宣称完整发布验收通过;数据、视频和权重不纳入。
This commit is contained in:
2026-09-15 16:29:17 +08:00
parent 8e7ab5fc76
commit f5d84e26f6
26 changed files with 2662 additions and 7 deletions
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env bash
# Reuse Isaac's pinned Python/Torch rather than replacing its CUDA dependencies.
set -euo pipefail
ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
LAUNCHER=${ISAACSIM_PYTHON:-"$HOME/isaacsim/python.sh"}
if [[ ! -x "$LAUNCHER" ]]; then
printf 'BLOCKED: set ISAACSIM_PYTHON to an executable Isaac Sim python.sh launcher.\n' >&2
exit 2
fi
export PYTHONPATH="$ROOT/source/dex_workbench${PYTHONPATH:+:$PYTHONPATH}"
exec "$LAUNCHER" -m dex_workbench_imitation.cli "$@"
+52 -4
View File
@@ -56,6 +56,11 @@ def build_parser(add_app_launcher_args):
parser.add_argument("--manifest", type=Path, required=True, help="Original data/source manifest")
parser.add_argument("--hdf5", type=Path, help="Omit for explicitly synthetic diagnostic reference")
parser.add_argument("--episode", default="demo_000000")
parser.add_argument(
"--policy-checkpoint",
type=Path,
help="Optional reference ACT policy; requires held-out HDF5 and --full-episode",
)
parser.add_argument("--steps", type=int, default=480, help="Per repetition at 240Hz; two repetitions")
parser.add_argument(
"--full-episode", action="store_true", help="Require complete HDF5 duration; allow up to 32000 steps"
@@ -66,6 +71,8 @@ def build_parser(add_app_launcher_args):
parser.add_argument("--execute-experimental", action="store_true", help="Acknowledge uncalibrated controller")
parser.add_argument("--limits", type=Path, help="JSON fields of control.Limits, SI units; UNCALIBRATED")
parser.add_argument("--gui", action="store_true", help="Opt-in bright-hand/dark-backdrop Kit preview")
parser.add_argument("--source-camera", type=Path, help="Estimated source-camera NPZ; requires GUI and HDF5")
parser.add_argument("--camera-time-factor", type=float, default=1.0, help="Must match HDF5 time stretch")
add_app_launcher_args(parser)
# None distinguishes omitted flags from explicit --headless / --viz none.
parser.set_defaults(headless=None, visualizer=None)
@@ -79,6 +86,10 @@ def main():
args = parser.parse_args()
if not args.execute_experimental:
parser.error("Require --execute-experimental")
if args.source_camera and (not args.gui or args.hdf5 is None):
parser.error("--source-camera requires --gui and --hdf5")
if args.policy_checkpoint and (args.hdf5 is None or not args.full_episode):
parser.error("--policy-checkpoint requires --hdf5 and --full-episode")
try:
configure_presentation(args)
validate_replay_request(args.steps, args.full_episode, args.hdf5 is not None, args.workspace_radius)
@@ -113,6 +124,11 @@ def main():
data = load(args.hdf5, manifest) if args.hdf5 else synthetic(manifest)
episode = data.episodes[args.episode]
validate_reference(episode, limits)
policy = None
if args.policy_checkpoint:
from dex_workbench_imitation.policy import ClosedLoopReferenceACT
policy = ClosedLoopReferenceACT(args.policy_checkpoint, manifest, args.hdf5, args.episode, episode, limits)
dt = 1 / 240
validate_replay_duration(args.steps, dt, episode.time[-1], args.full_episode)
reference = sample(episode, np.arange(args.steps + 1) * dt)
@@ -192,6 +208,13 @@ def main():
eye, center = create_preview(sim.stage, episode.wrist_position)
sim.set_camera_view(eye, center)
source_camera = None
if args.source_camera:
from dex_workbench_tracking.source_camera import SourceCamera
# The old backdrop is oriented for the default showcase camera, not this moving camera.
sim.stage.RemovePrim("/World/PreviewBackdrop")
source_camera = SourceCamera(sim.stage, args.source_camera, episode.time, args.camera_time_factor)
sim.reset()
if args.gui:
sim.render()
@@ -231,6 +254,8 @@ def main():
traces, reset_states, summaries = [], [], []
for repetition in range(2):
failure_context = {"phase": "reset", "completed_repetitions": repetition, "repetition": repetition}
if policy is not None:
policy.reset()
hand.reset()
hand.permanent_wrench_composer.reset()
hand.instantaneous_wrench_composer.reset()
@@ -266,9 +291,17 @@ def main():
}
require(app.is_running(), "Application stopped before finite test completed")
pose, velocity, q = state()
if policy is None:
target_position, target_quaternion, target_joints = (
reference.wrist_position[step],
reference.wrist_quaternion[step],
reference.joint_position[step],
)
else:
target_position, target_quaternion, target_joints = policy.target(step, pose, q)
force, torque = wrench(
reference.wrist_position[step],
reference.wrist_quaternion[step],
target_position,
target_quaternion,
pose,
velocity,
array(hand.data.root_com_pose_w)[0, :3],
@@ -283,9 +316,11 @@ def main():
is_global=True,
)
hand.set_joint_position_target_index(
target=tensor(reference.joint_position[step : step + 1, master_columns]), joint_ids=master_ids
target=tensor(target_joints[None, master_columns]), joint_ids=master_ids
)
hand.write_data_to_sim()
if source_camera is not None and step % 8 == 0:
source_camera.update((step + 1) * dt)
sim.step(render=args.gui and step % 8 == 0)
hand.update(dt)
pose, velocity, q = state()
@@ -347,6 +382,11 @@ def main():
"rms_errors_m_rad_rad_rad": np.sqrt((errors**2).mean(axis=0)).tolist(),
}
)
if policy is not None:
summaries[-1]["policy"] = policy.summary()
if source_camera is not None:
source_camera.update(args.steps * dt)
sim.render()
failure_context = {"phase": "repeatability", "completed_repetitions": 2, "steps_per_repetition": args.steps}
np.testing.assert_allclose(reset_states[0], reset_states[1], atol=1e-6, rtol=0)
position_end = 7 + len(names)
@@ -358,7 +398,15 @@ def main():
json.dumps(
{
"status": "PASS",
"check": "bounded_experimental_dynamic_tracking",
"check": "bounded_reference_act_rollout"
if policy is not None
else "bounded_experimental_dynamic_tracking",
"source_camera_sha256": hashlib.sha256(args.source_camera.read_bytes()).hexdigest()
if args.source_camera else None,
"camera_time_factor": args.camera_time_factor if args.source_camera else None,
"camera_sampling": "source_frame_hold" if args.source_camera else None,
"control_source": "reference_act" if policy is not None else "reference_targets",
"policy_checkpoint_sha256": policy.checkpoint_hash if policy is not None else None,
"runtime_verified_for_this_run_only": True,
"provenance": data.metadata["provenance"],
"reference_source": "hdf5" if args.hdf5 else "analytic_in_memory",