返回 JoyAI-Echo
action_condition.py
根目录 / echo_wm / helpers / action_condition.py
1 """Build the private training-space camera condition required by pure UCPE.
2
3 The public API exposes only semantic camera controls. The internal calibration
4 keeps the action trajectory in the same numerical regime as the released model.
5 """
6
7 from __future__ import annotations
8
9 import torch
10
11 from ltx_core.model.transformer.transformer import ActionBlockConfig
12
13 from .action_camera import build_action_pt_from_string
14
15 _INTERNAL_TRANSLATION_CALIBRATION = 30.0
16 _TEMPORAL_COMPRESSION = 8
17
18
19 def action_config(width: int, height: int, num_blocks: int = 48) -> ActionBlockConfig:
20 return ActionBlockConfig(
21 enabled=True,
22 block_indices=list(range(num_blocks)),
23 ucpe=True,
24 ucpe_attn_dim=1024,
25 ucpe_num_heads=8,
26 ucpe_patches_x=width // 32,
27 ucpe_patches_y=height // 32,
28 ucpe_image_width=width,
29 ucpe_image_height=height,
30 ucpe_freq_base=100.0,
31 ucpe_freq_scale=1.0,
32 )
33
34
35 def _normalize_trajectory(c2ws: torch.Tensor) -> torch.Tensor:
36 anchored = torch.linalg.inv(c2ws[:, 0:1]) @ c2ws
37 result = anchored.clone()
38 result[..., :3, 3] /= _INTERNAL_TRANSLATION_CALIBRATION
39 return result
40
41
42 def _build_action_condition(
43 action: str,
44 *,
45 num_frames: int,
46 width: int,
47 height: int,
48 translation_speed: float,
49 rotation_speed_deg: float,
50 pitch_limit_deg: float,
51 fov_deg: float,
52 device: torch.device,
53 fps: float,
54 output_dtype: torch.dtype,
55 ) -> dict[str, torch.Tensor]:
56 pt_data = build_action_pt_from_string(
57 action,
58 num_frames=num_frames,
59 image_width=width,
60 image_height=height,
61 translation_speed=translation_speed,
62 rotation_speed_deg=rotation_speed_deg,
63 pitch_limit_deg=pitch_limit_deg,
64 fov_deg=fov_deg,
65 fps=fps,
66 )
67 c2ws = pt_data["c2ws_raw"].unsqueeze(0).to(device=device, dtype=torch.float32)
68 K = pt_data["K_pix"].unsqueeze(0).to(device=device, dtype=torch.float32)
69 c2ws = _normalize_trajectory(c2ws)
70 latent_frames = (num_frames + 7) // _TEMPORAL_COMPRESSION
71 # The released UCPE checkpoint was trained with the stored c2w convention.
72 # Keep this internal compatibility choice out of the public configuration.
73 viewmats = c2ws[:, ::_TEMPORAL_COMPRESSION][:, :latent_frames]
74 Ks = K.unsqueeze(1).expand(-1, latent_frames, -1, -1).contiguous()
75 return {
76 "ucpe_viewmats": viewmats.to(dtype=output_dtype),
77 "ucpe_Ks": Ks.to(dtype=output_dtype),
78 }
79
80
81 def build_action_condition(
82 action: str,
83 *,
84 num_frames: int,
85 width: int,
86 height: int,
87 translation_speed: float,
88 rotation_speed_deg: float,
89 pitch_limit_deg: float,
90 fov_deg: float,
91 device: torch.device,
92 fps: float,
93 ) -> dict[str, torch.Tensor]:
94 return _build_action_condition(
95 action,
96 num_frames=num_frames,
97 width=width,
98 height=height,
99 translation_speed=translation_speed,
100 rotation_speed_deg=rotation_speed_deg,
101 pitch_limit_deg=pitch_limit_deg,
102 fov_deg=fov_deg,
103 device=device,
104 fps=fps,
105 output_dtype=torch.bfloat16,
106 )
107
108
109 def build_causal_action_condition(
110 action: str,
111 *,
112 num_frames: int,
113 width: int,
114 height: int,
115 translation_speed: float,
116 rotation_speed_deg: float,
117 pitch_limit_deg: float,
118 fov_deg: float,
119 device: torch.device,
120 fps: float,
121 ) -> dict[str, torch.Tensor]:
122 """Build the FP32 camera path used by bounded anchor translation."""
123 return _build_action_condition(
124 action,
125 num_frames=num_frames,
126 width=width,
127 height=height,
128 translation_speed=translation_speed,
129 rotation_speed_deg=rotation_speed_deg,
130 pitch_limit_deg=pitch_limit_deg,
131 fov_deg=fov_deg,
132 device=device,
133 fps=fps,
134 output_dtype=torch.float32,
135 )
136
137
138 def build_action_trajectory(
139 action: str,
140 *,
141 num_frames: int,
142 translation_speed: float,
143 rotation_speed_deg: float,
144 pitch_limit_deg: float,
145 fps: float,
146 ) -> torch.Tensor:
147 """Return the unnormalized camera-to-world path for optional HUD rendering."""
148 pt_data = build_action_pt_from_string(
149 action,
150 num_frames=num_frames,
151 image_width=1,
152 image_height=1,
153 translation_speed=translation_speed,
154 rotation_speed_deg=rotation_speed_deg,
155 pitch_limit_deg=pitch_limit_deg,
156 fov_deg=70.0,
157 fps=fps,
158 )
159 return pt_data["c2ws_raw"]
160
161
162 def validate_action_checkpoint_keys(keys: list[str]) -> None:
163 forbidden = ("plucker", "fine_proj", "kbd_", "action_encoder", "cam_")
164 leaked = [key for key in keys if any(token in key.lower() for token in forbidden)]
165 if leaked:
166 raise ValueError(f"Checkpoint contains unsupported action parameters: {leaked[:5]}")
167
167 lines PYTHON