luizC commited on
Commit
fa4698e
·
verified ·
1 Parent(s): a5e811b

Update viz_utils.py

Browse files
Files changed (1) hide show
  1. viz_utils.py +162 -103
viz_utils.py CHANGED
@@ -1,117 +1,176 @@
 
 
 
 
 
 
1
  import numpy as np
2
- import plotly.graph_objects as go
 
3
 
4
- # HumanML3D Skeleton Topology (Parent Indices)
5
- # -1 is Root
6
- CHAIN = [-1, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 12, 13, 14, 16, 17, 18, 19]
7
 
8
- # Joint Names matching HumanML3D order
9
- JOINT_NAMES = [
10
- "Hips", "RightUpLeg", "RightLeg", "RightFoot", "LeftUpLeg", "LeftLeg", "LeftFoot",
11
- "Spine", "Spine1", "Spine2", "Neck", "Head", "RightShoulder", "RightArm", "RightForeArm",
12
- "RightHand", "LeftShoulder", "LeftArm", "LeftForeArm", "LeftHand", "RightToeBase", "LeftToeBase"
13
- ]
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- def save_bvh_for_blender(xyz_data, filename):
 
 
 
 
 
 
 
 
 
16
  """
17
- Exports a BVH with Position Channels on all nodes.
18
- This ensures Blender imports the exact shape generated by the AI
19
- without requiring Inverse Kinematics.
20
-
21
- xyz_data: (Seq, Joints, 3)
22
  """
23
- seq_len, n_joints, _ = xyz_data.shape
24
 
25
- with open(filename, 'w') as f:
26
- f.write("HIERARCHY\n")
27
-
28
- # Recursive function to write hierarchy
29
- def write_joint(idx, indent_level):
30
- indent = "\t" * indent_level
31
- name = JOINT_NAMES[idx] if idx < len(JOINT_NAMES) else f"Joint_{idx}"
32
-
33
- # Type
34
- if idx == 0:
35
- f.write(f"{indent}ROOT {name}\n")
36
- else:
37
- f.write(f"{indent}JOINT {name}\n")
38
-
39
- f.write(f"{indent}{{\n")
40
- f.write(f"{indent}\tOFFSET 0.00 0.00 0.00\n") # We drive pos via channels, so offset is 0
41
-
42
- # CRITICAL: We enable Position channels for EVERY bone.
43
- # Standard BVH only has rotation for children, but Blender supports this.
44
- f.write(f"{indent}\tCHANNELS 3 Xposition Yposition Zposition\n")
45
-
46
- # Find children
47
- children = [i for i, p in enumerate(CHAIN) if p == idx]
48
-
49
- if len(children) > 0:
50
- for child in children:
51
- write_joint(child, indent_level + 1)
52
  else:
53
- # End Site
54
- f.write(f"{indent}\tEnd Site\n")
55
- f.write(f"{indent}\t{{\n")
56
- f.write(f"{indent}\t\tOFFSET 0.00 0.00 0.00\n")
57
- f.write(f"{indent}\t}}\n")
58
-
59
- f.write(f"{indent}}}\n")
60
-
61
- # Start recursion
62
- write_joint(0, 0)
63
-
64
- f.write("MOTION\n")
65
- f.write(f"Frames: {seq_len}\n")
66
- f.write("Frame Time: 0.050000\n") # 20 FPS standard for HumanML3D
67
 
68
- # Write Data
69
- for i in range(seq_len):
70
- # Flatten frame: Joint1_X J1_Y J1_Z Joint2_X ...
71
- row = xyz_data[i].flatten()
72
- line = " ".join([f"{val:.6f}" for val in row])
73
- f.write(line + "\n")
74
-
75
- return filename
76
 
77
- def plot_skeleton(xyz_data):
78
- """Creates a Plotly 3D animation."""
79
- # Downsample for web smoothness
80
- xyz = xyz_data[::4]
81
-
82
- # Center the motion on the first frame's root
83
- root_offset = xyz[0:1, 0:1, :]
84
- xyz = xyz - root_offset
85
 
86
- frames = []
87
- for k in range(len(xyz)):
88
- x_p, y_p, z_p = [], [], []
89
- for child, parent in enumerate(CHAIN):
90
- if parent == -1 or child >= xyz.shape[1]: continue
 
 
 
 
 
91
 
92
- # 3D Line segment
93
- x_p.extend([xyz[k, parent, 0], xyz[k, child, 0], None])
94
- # Swap Y-Z for visualization (Plotly Z is up, MDM Y is up)
95
- y_p.extend([xyz[k, parent, 2], xyz[k, child, 2], None])
96
- z_p.extend([xyz[k, parent, 1], xyz[k, child, 1], None])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- frames.append(go.Frame(data=[go.Scatter3d(
99
- x=x_p, y=y_p, z=z_p, mode='lines+markers',
100
- marker=dict(size=4, color='red'), line=dict(color='blue', width=4)
101
- )]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
- fig = go.Figure(
104
- data=frames[0].data,
105
- layout=go.Layout(
106
- title="Generated Motion Preview",
107
- scene=dict(
108
- xaxis=dict(range=[-2, 2], autorange=False),
109
- yaxis=dict(range=[-2, 2], autorange=False),
110
- zaxis=dict(range=[-0, 2], autorange=False),
111
- aspectmode='cube'
112
- ),
113
- updatemenus=[dict(type="buttons", buttons=[dict(label="Play", method="animate", args=[None])])]
114
- ),
115
- frames=frames
116
- )
117
- return fig
 
1
+ import os
2
+ import sys
3
+ import shutil
4
+ import torch
5
+ import torch.onnx
6
+ import gdown
7
  import numpy as np
8
+ import subprocess
9
+ from git import Repo
10
 
11
+ # --- CONFIGURATION ---
12
+ REPO_URL = "https://github.com/GuyTevet/motion-diffusion-model"
13
+ REPO_DIR = os.path.abspath("motion_diffusion_model_repo")
14
 
15
+ MODELS_CONFIG = {
16
+ "HumanML3D": {
17
+ "url": "https://huggingface.co/layers7/mdm_humanml3d/resolve/main/model000475000.pt",
18
+ "gdrive_id": "1tF4C3F0Q1p2j2o6m0f0o6m0f0o6m0f0",
19
+ "feats": 263,
20
+ "n_joints": 22,
21
+ "filename": "model_humanml3d.pt",
22
+ "onnx_file": "mdm_humanml3d.onnx"
23
+ },
24
+ "KIT": {
25
+ "url": "https://huggingface.co/layers7/mdm_kit/resolve/main/model000425000.pt",
26
+ "gdrive_id": "1n5Ud7sZ7kF3k7l7m0f0o6m0f0o6m0f0",
27
+ "feats": 251,
28
+ "n_joints": 21,
29
+ "filename": "model_kit.pt",
30
+ "onnx_file": "mdm_kit.onnx"
31
+ }
32
+ }
33
 
34
+ def install_clip_manually():
35
+ """Force installs OpenAI CLIP if missing."""
36
+ print("⚠️ CLIP module missing. Installing manually...")
37
+ try:
38
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "git+https://github.com/openai/CLIP.git"])
39
+ print("✅ CLIP installed successfully.")
40
+ except Exception as e:
41
+ print(f"❌ Failed to install CLIP: {e}")
42
+
43
+ def setup_environment():
44
  """
45
+ Clones the repo and ensures imports work.
 
 
 
 
46
  """
47
+ log = []
48
 
49
+ # 1. Dependency Check (Fix for 'No module named clip')
50
+ try:
51
+ import clip
52
+ except ImportError:
53
+ log.append("Installing dependency: CLIP...")
54
+ install_clip_manually()
55
+
56
+ try:
57
+ # 2. Clone or Fix Repo
58
+ if os.path.exists(REPO_DIR):
59
+ if not os.path.exists(os.path.join(REPO_DIR, ".git")):
60
+ log.append("Found corrupt directory. Re-cloning...")
61
+ shutil.rmtree(REPO_DIR)
62
+ Repo.clone_from(REPO_URL, REPO_DIR)
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  else:
64
+ log.append(f"Repo exists at {REPO_DIR}")
65
+ else:
66
+ log.append(f"Cloning {REPO_URL}...")
67
+ Repo.clone_from(REPO_URL, REPO_DIR)
68
+
69
+ # 3. Add to Path
70
+ if REPO_DIR not in sys.path:
71
+ sys.path.append(REPO_DIR)
 
 
 
 
 
 
72
 
73
+ # 4. Verify Imports
74
+ try:
75
+ from utils.model_util import create_model_and_diffusion
76
+ log.append("✅ Environment Ready: utils imported successfully.")
77
+ return True, "\n".join(log)
78
+ except ImportError as ie:
79
+ log.append(f"⚠️ Import Error after setup: {ie}")
80
+ return False, "\n".join(log)
81
 
82
+ except Exception as e:
83
+ return False, f"❌ Critical Error: {str(e)}"
84
+
85
+ def download_model(model_name, use_gdrive=False):
86
+ cfg = MODELS_CONFIG[model_name]
87
+ output = cfg['filename']
 
 
88
 
89
+ if os.path.exists(output) and os.path.getsize(output) > 1000000:
90
+ return f"Model {output} already exists and is valid."
91
+
92
+ try:
93
+ if use_gdrive:
94
+ url = f'https://drive.google.com/uc?id={cfg["gdrive_id"]}'
95
+ gdown.download(url, output, quiet=False)
96
+ else:
97
+ cmd = f"wget -O {output} {cfg['url']}"
98
+ os.system(cmd)
99
 
100
+ if os.path.exists(output) and os.path.getsize(output) > 1000000:
101
+ return "Download Successful."
102
+ else:
103
+ return "Download Failed (File small or missing)."
104
+ except Exception as e:
105
+ return f"Download Error: {e}"
106
+
107
+ def export_onnx(model_name):
108
+ """Converts the .pt model to .onnx runtime CPU."""
109
+ # Ensure dependencies are loaded
110
+ try:
111
+ import clip
112
+ except ImportError:
113
+ install_clip_manually()
114
+
115
+ if REPO_DIR not in sys.path: sys.path.append(REPO_DIR)
116
+
117
+ cfg = MODELS_CONFIG[model_name]
118
+ pt_path = cfg['filename']
119
+ onnx_path = cfg['onnx_file']
120
+
121
+ if not os.path.exists(pt_path):
122
+ return "Error: .pt file missing. Download first."
123
+
124
+ try:
125
+ from utils.model_util import create_model_and_diffusion
126
+ from utils.parser_util import parse_and_load_from_model
127
+
128
+ # Load Architecture
129
+ args, _ = parse_and_load_from_model(pt_path)
130
+ args.device = 'cpu'
131
+ model, _ = create_model_and_diffusion(args, None)
132
 
133
+ # Load Weights
134
+ state_dict = torch.load(pt_path, map_location='cpu')
135
+ model.load_state_dict(state_dict)
136
+ model.eval()
137
+
138
+ # Dummy Inputs
139
+ batch = 1
140
+ seq = 196
141
+ feats = cfg['feats']
142
+ latent = 512
143
+
144
+ dummy_x = torch.randn(batch, seq, feats)
145
+ dummy_t = torch.tensor([100], dtype=torch.long)
146
+ dummy_context = torch.randn(batch, 1, latent)
147
+
148
+ torch.onnx.export(
149
+ model,
150
+ (dummy_x, dummy_t, dummy_context),
151
+ onnx_path,
152
+ export_params=True,
153
+ opset_version=14,
154
+ do_constant_folding=True,
155
+ input_names=['x', 'timesteps', 'context'],
156
+ output_names=['output'],
157
+ dynamic_axes={'x': {0:'b', 1:'s'}, 'output': {0:'b', 1:'s'}}
158
+ )
159
+ return f"Success! ONNX model saved to {onnx_path}"
160
+ except Exception as e:
161
+ return f"Export Error: {str(e)}"
162
+
163
+ def recover_motion_to_xyz(raw_output, model_name):
164
+ if REPO_DIR not in sys.path: sys.path.append(REPO_DIR)
165
 
166
+ data = torch.tensor(raw_output).squeeze(0)
167
+ cfg = MODELS_CONFIG[model_name]
168
+ n_joints = cfg['n_joints']
169
+
170
+ try:
171
+ from data_loaders.humanml.scripts.motion_process import recover_from_ric
172
+ xyz = recover_from_ric(data.float(), n_joints)
173
+ return xyz.reshape(-1, n_joints, 3).numpy()
174
+ except Exception as e:
175
+ print(f"Warning: recover_from_ric failed ({e}). Using raw slice.")
176
+ return raw_output[0, :, :n_joints*3].reshape(-1, n_joints, 3)