Spaces:
Running on L40S
Running on L40S
Add inverse dynamics support
Browse files- cosmos-framework/cosmos_framework/data/vfm/action/action_viz/app.py +39 -8
- cosmos-framework/cosmos_framework/data/vfm/action/action_viz/app_test.py +52 -0
- cosmos-framework/cosmos_framework/data/vfm/action/action_viz/local_worker.py +4 -1
- cosmos-framework/cosmos_framework/data/vfm/action/action_viz/local_worker_test.py +106 -1
- cosmos-framework/cosmos_framework/data/vfm/action/action_viz/state.py +9 -2
- cosmos-framework/cosmos_framework/data/vfm/action/action_viz/state_test.py +34 -0
cosmos-framework/cosmos_framework/data/vfm/action/action_viz/app.py
CHANGED
|
@@ -39,6 +39,7 @@ from cosmos_framework.data.vfm.action.action_viz.state import (
|
|
| 39 |
GenerationResult,
|
| 40 |
control_points_from_action,
|
| 41 |
make_generation_id,
|
|
|
|
| 42 |
read_generated_action,
|
| 43 |
)
|
| 44 |
from cosmos_framework.data.vfm.action.urdf_visualizer.unified_action import ActionFormat, get_video_from_sample
|
|
@@ -284,6 +285,7 @@ def launch_action_viz(config: AppConfig) -> None:
|
|
| 284 |
num_steps_input = client.gui.add_number("Sampling steps", initial_value=DEFAULT_SAMPLING_STEPS, min=1, step=1)
|
| 285 |
guidance_input = client.gui.add_number("Guidance", initial_value=DEFAULT_GUIDANCE, min=0.0, max=7.0, step=0.1)
|
| 286 |
generate_button = client.gui.add_button("Run forward dynamics")
|
|
|
|
| 287 |
policy_button = client.gui.add_button("Run policy")
|
| 288 |
status_text = client.gui.add_markdown(f"*{worker_client.status_message}*")
|
| 289 |
info_text = client.gui.add_markdown("")
|
|
@@ -300,6 +302,7 @@ def launch_action_viz(config: AppConfig) -> None:
|
|
| 300 |
with client.gui.add_folder("Video"):
|
| 301 |
gt_panel = client.gui.add_image(np.zeros((64, 64, 3), dtype=np.uint8), label="Ground truth")
|
| 302 |
generated_panel = client.gui.add_image(np.zeros((64, 64, 3), dtype=np.uint8), label="Generated")
|
|
|
|
| 303 |
|
| 304 |
with client.gui.add_folder("Trajectory editor"):
|
| 305 |
target_dropdown = client.gui.add_dropdown("Trajectory", options=("right", "left"), initial_value="right")
|
|
@@ -696,8 +699,12 @@ def launch_action_viz(config: AppConfig) -> None:
|
|
| 696 |
return
|
| 697 |
if loaded.video is not None and t < len(loaded.video):
|
| 698 |
gt_panel.image = loaded.video[t]
|
| 699 |
-
|
| 700 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 701 |
|
| 702 |
def _update_action_text(t: int) -> None:
|
| 703 |
loaded = sample_state["loaded"]
|
|
@@ -750,6 +757,7 @@ def launch_action_viz(config: AppConfig) -> None:
|
|
| 750 |
_flush_scene_update()
|
| 751 |
loaded.generated_video = []
|
| 752 |
generated_panel.image = np.zeros((64, 64, 3), dtype=np.uint8)
|
|
|
|
| 753 |
|
| 754 |
generation_id = make_generation_id()
|
| 755 |
client_generation_root = config.output_root / "active" / f"client_{client.client_id}"
|
|
@@ -802,13 +810,16 @@ def launch_action_viz(config: AppConfig) -> None:
|
|
| 802 |
)
|
| 803 |
|
| 804 |
try:
|
| 805 |
-
if result.status == "success"
|
| 806 |
-
|
| 807 |
-
|
|
|
|
|
|
|
|
|
|
| 808 |
try:
|
| 809 |
-
_replace_loaded_action(loaded,
|
| 810 |
-
except
|
| 811 |
-
status_text.content = f"*Generation {generation_id}
|
| 812 |
else:
|
| 813 |
status_text.content = f"*Generation {generation_id} complete.*"
|
| 814 |
else:
|
|
@@ -846,6 +857,10 @@ def launch_action_viz(config: AppConfig) -> None:
|
|
| 846 |
def _(_) -> None:
|
| 847 |
run_generation("forward_dynamics")
|
| 848 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 849 |
@policy_button.on_click
|
| 850 |
def _(_) -> None:
|
| 851 |
run_generation("policy")
|
|
@@ -1021,6 +1036,22 @@ def _set_loaded_action(loaded: LoadedSample, raw_action: np.ndarray) -> None:
|
|
| 1021 |
loaded.baked_action = raw_action.astype(np.float32).copy()
|
| 1022 |
|
| 1023 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1024 |
def _build_render_scene(loaded: LoadedSample, *, source_robot_animation: bool) -> Any:
|
| 1025 |
"""Build a render scene from the loaded sample's current editor state."""
|
| 1026 |
|
|
|
|
| 39 |
GenerationResult,
|
| 40 |
control_points_from_action,
|
| 41 |
make_generation_id,
|
| 42 |
+
model_mode_generates_action,
|
| 43 |
read_generated_action,
|
| 44 |
)
|
| 45 |
from cosmos_framework.data.vfm.action.urdf_visualizer.unified_action import ActionFormat, get_video_from_sample
|
|
|
|
| 285 |
num_steps_input = client.gui.add_number("Sampling steps", initial_value=DEFAULT_SAMPLING_STEPS, min=1, step=1)
|
| 286 |
guidance_input = client.gui.add_number("Guidance", initial_value=DEFAULT_GUIDANCE, min=0.0, max=7.0, step=0.1)
|
| 287 |
generate_button = client.gui.add_button("Run forward dynamics")
|
| 288 |
+
inverse_dynamics_button = client.gui.add_button("Run inverse dynamics")
|
| 289 |
policy_button = client.gui.add_button("Run policy")
|
| 290 |
status_text = client.gui.add_markdown(f"*{worker_client.status_message}*")
|
| 291 |
info_text = client.gui.add_markdown("")
|
|
|
|
| 302 |
with client.gui.add_folder("Video"):
|
| 303 |
gt_panel = client.gui.add_image(np.zeros((64, 64, 3), dtype=np.uint8), label="Ground truth")
|
| 304 |
generated_panel = client.gui.add_image(np.zeros((64, 64, 3), dtype=np.uint8), label="Generated")
|
| 305 |
+
generated_panel.visible = False
|
| 306 |
|
| 307 |
with client.gui.add_folder("Trajectory editor"):
|
| 308 |
target_dropdown = client.gui.add_dropdown("Trajectory", options=("right", "left"), initial_value="right")
|
|
|
|
| 699 |
return
|
| 700 |
if loaded.video is not None and t < len(loaded.video):
|
| 701 |
gt_panel.image = loaded.video[t]
|
| 702 |
+
generated_frame = _select_generated_video_frame(loaded.generated_video, t)
|
| 703 |
+
if generated_frame is None:
|
| 704 |
+
generated_panel.visible = False
|
| 705 |
+
else:
|
| 706 |
+
generated_panel.image = generated_frame
|
| 707 |
+
generated_panel.visible = True
|
| 708 |
|
| 709 |
def _update_action_text(t: int) -> None:
|
| 710 |
loaded = sample_state["loaded"]
|
|
|
|
| 757 |
_flush_scene_update()
|
| 758 |
loaded.generated_video = []
|
| 759 |
generated_panel.image = np.zeros((64, 64, 3), dtype=np.uint8)
|
| 760 |
+
generated_panel.visible = False
|
| 761 |
|
| 762 |
generation_id = make_generation_id()
|
| 763 |
client_generation_root = config.output_root / "active" / f"client_{client.client_id}"
|
|
|
|
| 810 |
)
|
| 811 |
|
| 812 |
try:
|
| 813 |
+
if result.status == "success":
|
| 814 |
+
if result.video_path:
|
| 815 |
+
loaded.generated_video = _load_video_frames(Path(result.video_path))
|
| 816 |
+
else:
|
| 817 |
+
loaded.generated_video = []
|
| 818 |
+
if model_mode_generates_action(model_mode):
|
| 819 |
try:
|
| 820 |
+
_replace_loaded_action(loaded, _read_generated_result_action(result))
|
| 821 |
+
except ValueError as exc:
|
| 822 |
+
status_text.content = f"*Generation {generation_id} failed: {exc}*"
|
| 823 |
else:
|
| 824 |
status_text.content = f"*Generation {generation_id} complete.*"
|
| 825 |
else:
|
|
|
|
| 857 |
def _(_) -> None:
|
| 858 |
run_generation("forward_dynamics")
|
| 859 |
|
| 860 |
+
@inverse_dynamics_button.on_click
|
| 861 |
+
def _(_) -> None:
|
| 862 |
+
run_generation("inverse_dynamics")
|
| 863 |
+
|
| 864 |
@policy_button.on_click
|
| 865 |
def _(_) -> None:
|
| 866 |
run_generation("policy")
|
|
|
|
| 1036 |
loaded.baked_action = raw_action.astype(np.float32).copy()
|
| 1037 |
|
| 1038 |
|
| 1039 |
+
def _read_generated_result_action(result: GenerationResult) -> np.ndarray:
|
| 1040 |
+
"""Read the raw action emitted by an action-generating model result."""
|
| 1041 |
+
|
| 1042 |
+
if result.generated_action_path is None:
|
| 1043 |
+
raise ValueError(f"Generation {result.generation_id} has no saved generated action")
|
| 1044 |
+
return read_generated_action(Path(result.generated_action_path))
|
| 1045 |
+
|
| 1046 |
+
|
| 1047 |
+
def _select_generated_video_frame(generated_video: list[np.ndarray], t: int) -> np.ndarray | None:
|
| 1048 |
+
"""Return the displayed generated-video frame, or None when no video exists."""
|
| 1049 |
+
|
| 1050 |
+
if not generated_video:
|
| 1051 |
+
return None
|
| 1052 |
+
return generated_video[min(max(int(t), 0), len(generated_video) - 1)]
|
| 1053 |
+
|
| 1054 |
+
|
| 1055 |
def _build_render_scene(loaded: LoadedSample, *, source_robot_animation: bool) -> Any:
|
| 1056 |
"""Build a render scene from the loaded sample's current editor state."""
|
| 1057 |
|
cosmos-framework/cosmos_framework/data/vfm/action/action_viz/app_test.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
from cosmos_framework.data.vfm.action.action_viz.app import (
|
| 7 |
+
_read_generated_result_action,
|
| 8 |
+
_select_generated_video_frame,
|
| 9 |
+
)
|
| 10 |
+
from cosmos_framework.data.vfm.action.action_viz.state import (
|
| 11 |
+
BRIDGE_ACTION_DIM,
|
| 12 |
+
GenerationResult,
|
| 13 |
+
write_generated_action,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@pytest.mark.L0
|
| 18 |
+
def test_read_generated_result_action_supports_inverse_dynamics(tmp_path) -> None:
|
| 19 |
+
action = np.ones((2, BRIDGE_ACTION_DIM), dtype=np.float32)
|
| 20 |
+
action_path = write_generated_action(action, tmp_path)
|
| 21 |
+
result = GenerationResult(
|
| 22 |
+
generation_id="gen",
|
| 23 |
+
model_mode="inverse_dynamics",
|
| 24 |
+
status="success",
|
| 25 |
+
generated_action_path=str(action_path),
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
loaded = _read_generated_result_action(result)
|
| 29 |
+
|
| 30 |
+
np.testing.assert_allclose(loaded, action, atol=1e-6)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@pytest.mark.L0
|
| 34 |
+
def test_read_generated_result_action_requires_saved_action_path() -> None:
|
| 35 |
+
result = GenerationResult(generation_id="gen", model_mode="inverse_dynamics", status="success")
|
| 36 |
+
|
| 37 |
+
with pytest.raises(ValueError, match="Generation gen has no saved generated action"):
|
| 38 |
+
_read_generated_result_action(result)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@pytest.mark.L0
|
| 42 |
+
def test_select_generated_video_frame_returns_none_for_action_only_generations() -> None:
|
| 43 |
+
assert _select_generated_video_frame([], 0) is None
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@pytest.mark.L0
|
| 47 |
+
def test_select_generated_video_frame_clamps_to_available_frames() -> None:
|
| 48 |
+
first = np.zeros((2, 2, 3), dtype=np.uint8)
|
| 49 |
+
last = np.ones((2, 2, 3), dtype=np.uint8)
|
| 50 |
+
|
| 51 |
+
np.testing.assert_array_equal(_select_generated_video_frame([first, last], -1), first)
|
| 52 |
+
np.testing.assert_array_equal(_select_generated_video_frame([first, last], 99), last)
|
cosmos-framework/cosmos_framework/data/vfm/action/action_viz/local_worker.py
CHANGED
|
@@ -21,6 +21,7 @@ import numpy as np
|
|
| 21 |
from cosmos_framework.data.vfm.action.action_viz.state import (
|
| 22 |
GenerationRequest,
|
| 23 |
GenerationResult,
|
|
|
|
| 24 |
request_to_json_dict,
|
| 25 |
write_generated_action,
|
| 26 |
write_generation_request,
|
|
@@ -957,7 +958,7 @@ def _copy_generation_outputs(
|
|
| 957 |
|
| 958 |
generated_video_path = None
|
| 959 |
vision_path = sample_output_dir / "vision.mp4"
|
| 960 |
-
if vision_path.is_file():
|
| 961 |
generated_video_path = output_dir / "generated.mp4"
|
| 962 |
shutil.copyfile(vision_path, generated_video_path)
|
| 963 |
|
|
@@ -975,6 +976,8 @@ def _copy_generation_outputs(
|
|
| 975 |
to_model_space=False,
|
| 976 |
)
|
| 977 |
generated_action_path = write_generated_action(np.asarray(raw_action, dtype=np.float32), output_dir)
|
|
|
|
|
|
|
| 978 |
|
| 979 |
result_payload: dict[str, Any] = {
|
| 980 |
"ok": True,
|
|
|
|
| 21 |
from cosmos_framework.data.vfm.action.action_viz.state import (
|
| 22 |
GenerationRequest,
|
| 23 |
GenerationResult,
|
| 24 |
+
model_mode_generates_action,
|
| 25 |
request_to_json_dict,
|
| 26 |
write_generated_action,
|
| 27 |
write_generation_request,
|
|
|
|
| 958 |
|
| 959 |
generated_video_path = None
|
| 960 |
vision_path = sample_output_dir / "vision.mp4"
|
| 961 |
+
if request.model_mode != "inverse_dynamics" and vision_path.is_file():
|
| 962 |
generated_video_path = output_dir / "generated.mp4"
|
| 963 |
shutil.copyfile(vision_path, generated_video_path)
|
| 964 |
|
|
|
|
| 976 |
to_model_space=False,
|
| 977 |
)
|
| 978 |
generated_action_path = write_generated_action(np.asarray(raw_action, dtype=np.float32), output_dir)
|
| 979 |
+
if model_mode_generates_action(request.model_mode) and generated_action_path is None:
|
| 980 |
+
raise ValueError(f"{request.model_mode} generation did not return an action")
|
| 981 |
|
| 982 |
result_payload: dict[str, Any] = {
|
| 983 |
"ok": True,
|
cosmos-framework/cosmos_framework/data/vfm/action/action_viz/local_worker_test.py
CHANGED
|
@@ -1,10 +1,22 @@
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
|
|
|
|
|
|
| 3 |
from typing import Any
|
| 4 |
|
|
|
|
| 5 |
import pytest
|
| 6 |
|
| 7 |
-
from cosmos_framework.data.vfm.action.action_viz.local_worker import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
@pytest.mark.L0
|
|
@@ -24,3 +36,96 @@ def test_patched_unipc_progress_reports_sampler_percentages(monkeypatch: pytest.
|
|
| 24 |
|
| 25 |
assert updates == [(37, "sampling"), (63, "sampling"), (90, "sampling")]
|
| 26 |
assert unipc.progress_bar is passthrough_progress_bar
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from __future__ import annotations
|
| 2 |
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
from typing import Any
|
| 6 |
|
| 7 |
+
import numpy as np
|
| 8 |
import pytest
|
| 9 |
|
| 10 |
+
from cosmos_framework.data.vfm.action.action_viz.local_worker import (
|
| 11 |
+
_copy_generation_outputs,
|
| 12 |
+
_patched_unipc_progress,
|
| 13 |
+
)
|
| 14 |
+
from cosmos_framework.data.vfm.action.action_viz.state import (
|
| 15 |
+
BRIDGE_ACTION_DIM,
|
| 16 |
+
ControlPoint,
|
| 17 |
+
GenerationRequest,
|
| 18 |
+
read_generated_action,
|
| 19 |
+
)
|
| 20 |
|
| 21 |
|
| 22 |
@pytest.mark.L0
|
|
|
|
| 36 |
|
| 37 |
assert updates == [(37, "sampling"), (63, "sampling"), (90, "sampling")]
|
| 38 |
assert unipc.progress_bar is passthrough_progress_bar
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@pytest.mark.L0
|
| 42 |
+
def test_copy_generation_outputs_writes_inverse_dynamics_action_without_copying_auxiliary_video(tmp_path) -> None:
|
| 43 |
+
request = _request(tmp_path, "inverse_dynamics")
|
| 44 |
+
inference_dir = tmp_path / "framework_output"
|
| 45 |
+
sample_output_dir = inference_dir / request.generation_id
|
| 46 |
+
sample_output_dir.mkdir(parents=True)
|
| 47 |
+
sample_outputs = {
|
| 48 |
+
"outputs": [
|
| 49 |
+
{
|
| 50 |
+
"content": {
|
| 51 |
+
"action": [
|
| 52 |
+
[0.0] * BRIDGE_ACTION_DIM,
|
| 53 |
+
[0.0] * BRIDGE_ACTION_DIM,
|
| 54 |
+
]
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
]
|
| 58 |
+
}
|
| 59 |
+
(sample_output_dir / "sample_outputs.json").write_text(json.dumps(sample_outputs), encoding="utf-8")
|
| 60 |
+
(sample_output_dir / "vision.mp4").write_bytes(b"auxiliary video")
|
| 61 |
+
|
| 62 |
+
payload, video_path, action_path = _copy_generation_outputs(request, inference_dir, Path(request.output_dir))
|
| 63 |
+
|
| 64 |
+
assert payload["artifacts"]["video_filename"] is None
|
| 65 |
+
assert payload["artifacts"]["action_filename"] == "generated_action.json"
|
| 66 |
+
assert video_path is None
|
| 67 |
+
assert action_path is not None
|
| 68 |
+
action = read_generated_action(Path(action_path))
|
| 69 |
+
assert action.shape == (2, BRIDGE_ACTION_DIM)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@pytest.mark.L0
|
| 73 |
+
def test_copy_generation_outputs_keeps_policy_video(tmp_path) -> None:
|
| 74 |
+
request = _request(tmp_path, "policy")
|
| 75 |
+
output_dir = Path(request.output_dir)
|
| 76 |
+
output_dir.mkdir(parents=True)
|
| 77 |
+
inference_dir = tmp_path / "framework_output"
|
| 78 |
+
sample_output_dir = inference_dir / request.generation_id
|
| 79 |
+
sample_output_dir.mkdir(parents=True)
|
| 80 |
+
sample_outputs = {
|
| 81 |
+
"outputs": [
|
| 82 |
+
{
|
| 83 |
+
"content": {
|
| 84 |
+
"action": [
|
| 85 |
+
[0.0] * BRIDGE_ACTION_DIM,
|
| 86 |
+
]
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
]
|
| 90 |
+
}
|
| 91 |
+
(sample_output_dir / "sample_outputs.json").write_text(json.dumps(sample_outputs), encoding="utf-8")
|
| 92 |
+
(sample_output_dir / "vision.mp4").write_bytes(b"generated video")
|
| 93 |
+
|
| 94 |
+
payload, video_path, action_path = _copy_generation_outputs(request, inference_dir, output_dir)
|
| 95 |
+
|
| 96 |
+
assert payload["artifacts"]["video_filename"] == "generated.mp4"
|
| 97 |
+
assert video_path == str(output_dir / "generated.mp4")
|
| 98 |
+
assert Path(video_path).read_bytes() == b"generated video"
|
| 99 |
+
assert action_path is not None
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@pytest.mark.L0
|
| 103 |
+
def test_copy_generation_outputs_requires_action_for_inverse_dynamics(tmp_path) -> None:
|
| 104 |
+
request = _request(tmp_path, "inverse_dynamics")
|
| 105 |
+
inference_dir = tmp_path / "framework_output"
|
| 106 |
+
sample_output_dir = inference_dir / request.generation_id
|
| 107 |
+
sample_output_dir.mkdir(parents=True)
|
| 108 |
+
(sample_output_dir / "sample_outputs.json").write_text(
|
| 109 |
+
json.dumps({"outputs": [{"content": {}}]}),
|
| 110 |
+
encoding="utf-8",
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
with pytest.raises(ValueError, match="inverse_dynamics generation did not return an action"):
|
| 114 |
+
_copy_generation_outputs(request, inference_dir, Path(request.output_dir))
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _request(tmp_path, model_mode: str) -> GenerationRequest:
|
| 118 |
+
return GenerationRequest(
|
| 119 |
+
generation_id="gen",
|
| 120 |
+
model_mode=model_mode,
|
| 121 |
+
dataset="bridge",
|
| 122 |
+
sample_index=0,
|
| 123 |
+
experiment_name="",
|
| 124 |
+
s3_checkpoint_dir="nvidia/Cosmos3-Nano",
|
| 125 |
+
checkpoint_cache_dir=None,
|
| 126 |
+
output_dir=str(tmp_path / "output"),
|
| 127 |
+
seed=0,
|
| 128 |
+
num_steps=1,
|
| 129 |
+
control_points=[ControlPoint(frame=0, values=[0.0] * BRIDGE_ACTION_DIM)],
|
| 130 |
+
baked_action=np.zeros((1, BRIDGE_ACTION_DIM), dtype=np.float32).tolist(),
|
| 131 |
+
)
|
cosmos-framework/cosmos_framework/data/vfm/action/action_viz/state.py
CHANGED
|
@@ -24,7 +24,8 @@ BRIDGE_ACTION_CHANNELS = (
|
|
| 24 |
"gripper",
|
| 25 |
)
|
| 26 |
BRIDGE_DATASET_SELECTOR = "bridge_20260501"
|
| 27 |
-
GENERATION_MODEL_MODES = ("forward_dynamics", "policy")
|
|
|
|
| 28 |
|
| 29 |
|
| 30 |
@dataclass(frozen=True)
|
|
@@ -189,7 +190,7 @@ def read_generation_request(path: Path) -> GenerationRequest:
|
|
| 189 |
return request_from_json_dict(json.loads(path.read_text()))
|
| 190 |
|
| 191 |
|
| 192 |
-
def write_generated_action(action: np.ndarray, output_dir: Path, filename: str = "
|
| 193 |
"""Write a generated raw action array and return its path."""
|
| 194 |
|
| 195 |
action = _validate_generated_action_array(action)
|
|
@@ -231,6 +232,12 @@ def read_generated_action(path: Path) -> np.ndarray:
|
|
| 231 |
return _validate_generated_action_array(np.asarray(json.loads(path.read_text()), dtype=np.float32))
|
| 232 |
|
| 233 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
def _validate_action_array(action: np.ndarray, action_dim: int | None = None) -> np.ndarray:
|
| 235 |
action = np.asarray(action, dtype=np.float32)
|
| 236 |
expected_dim = BRIDGE_ACTION_DIM if action_dim is None else int(action_dim)
|
|
|
|
| 24 |
"gripper",
|
| 25 |
)
|
| 26 |
BRIDGE_DATASET_SELECTOR = "bridge_20260501"
|
| 27 |
+
GENERATION_MODEL_MODES = ("forward_dynamics", "inverse_dynamics", "policy")
|
| 28 |
+
ACTION_GENERATING_MODEL_MODES = ("inverse_dynamics", "policy")
|
| 29 |
|
| 30 |
|
| 31 |
@dataclass(frozen=True)
|
|
|
|
| 190 |
return request_from_json_dict(json.loads(path.read_text()))
|
| 191 |
|
| 192 |
|
| 193 |
+
def write_generated_action(action: np.ndarray, output_dir: Path, filename: str = "generated_action.json") -> Path:
|
| 194 |
"""Write a generated raw action array and return its path."""
|
| 195 |
|
| 196 |
action = _validate_generated_action_array(action)
|
|
|
|
| 232 |
return _validate_generated_action_array(np.asarray(json.loads(path.read_text()), dtype=np.float32))
|
| 233 |
|
| 234 |
|
| 235 |
+
def model_mode_generates_action(model_mode: str) -> bool:
|
| 236 |
+
"""Return whether a supported generation mode emits a raw action trajectory."""
|
| 237 |
+
|
| 238 |
+
return _validate_model_mode(model_mode) in ACTION_GENERATING_MODEL_MODES
|
| 239 |
+
|
| 240 |
+
|
| 241 |
def _validate_action_array(action: np.ndarray, action_dim: int | None = None) -> np.ndarray:
|
| 242 |
action = np.asarray(action, dtype=np.float32)
|
| 243 |
expected_dim = BRIDGE_ACTION_DIM if action_dim is None else int(action_dim)
|
cosmos-framework/cosmos_framework/data/vfm/action/action_viz/state_test.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pytest
|
| 5 |
+
|
| 6 |
+
from cosmos_framework.data.vfm.action.action_viz.state import (
|
| 7 |
+
BRIDGE_ACTION_DIM,
|
| 8 |
+
model_mode_generates_action,
|
| 9 |
+
read_generated_action,
|
| 10 |
+
write_generated_action,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@pytest.mark.L0
|
| 15 |
+
@pytest.mark.parametrize(
|
| 16 |
+
("model_mode", "generates_action"),
|
| 17 |
+
[
|
| 18 |
+
("forward_dynamics", False),
|
| 19 |
+
("inverse_dynamics", True),
|
| 20 |
+
("policy", True),
|
| 21 |
+
],
|
| 22 |
+
)
|
| 23 |
+
def test_model_mode_generates_action(model_mode: str, generates_action: bool) -> None:
|
| 24 |
+
assert model_mode_generates_action(model_mode) is generates_action
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@pytest.mark.L0
|
| 28 |
+
def test_generated_action_round_trip_uses_generic_filename(tmp_path) -> None:
|
| 29 |
+
action = np.arange(2 * BRIDGE_ACTION_DIM, dtype=np.float32).reshape(2, BRIDGE_ACTION_DIM)
|
| 30 |
+
|
| 31 |
+
path = write_generated_action(action, tmp_path)
|
| 32 |
+
|
| 33 |
+
assert path.name == "generated_action.json"
|
| 34 |
+
np.testing.assert_allclose(read_generated_action(path), action, atol=1e-6)
|