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:
@@ -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 "$@"
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user