I0u0I commited on
Commit
dcc6a29
·
verified ·
1 Parent(s): d98ae36

Add DrawMotion ZeroGPU Gradio adapter

Browse files
Files changed (4) hide show
  1. README.md +13 -5
  2. app.py +180 -0
  3. packages.txt +2 -0
  4. requirements.txt +16 -0
README.md CHANGED
@@ -1,13 +1,21 @@
1
  ---
2
  title: DrawMotion ZeroGPU
3
- emoji: 🏆
4
  colorFrom: blue
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 6.15.0
8
- python_version: '3.13'
9
  app_file: app.py
10
  pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
1
  ---
2
  title: DrawMotion ZeroGPU
3
+ emoji: 🏃
4
  colorFrom: blue
5
+ colorTo: green
6
  sdk: gradio
7
+ sdk_version: 5.49.1
 
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
+ short_description: Text and trajectory conditioned 3D human motion generation.
12
  ---
13
 
14
+ # DrawMotion ZeroGPU
15
+
16
+ This Space is a Gradio/ZeroGPU adapter for DrawMotion. It loads the public code
17
+ from `InvertedForest/DrawMotion` and the public model assets from
18
+ `I0u0I/DrawMotion`.
19
+
20
+ The first version exposes text plus trajectory presets/custom JSON and returns an
21
+ MP4 preview plus the generated joint JSON.
app.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import gradio as gr
8
+ import numpy as np
9
+ from huggingface_hub import snapshot_download
10
+
11
+ import spaces
12
+
13
+
14
+ ROOT = Path(__file__).resolve().parent
15
+ CODE_DIR = ROOT / "DrawMotion"
16
+ MODEL_REPO = "I0u0I/DrawMotion"
17
+ GIT_REPO = "https://github.com/InvertedForest/DrawMotion.git"
18
+
19
+ ASSET_PATTERNS = [
20
+ "logs/human_ml3d/last.ckpt",
21
+ "mid_feat/t2m/mid_feat.pt",
22
+ "stickman/weight/real_init/t2m/stickman_encoder.ckpt",
23
+ ]
24
+
25
+ EXAMPLES = {
26
+ "forward line": [[0, 0], [40, 0], [90, 0], [150, 0], [220, 0]],
27
+ "left arc": [[0, 0], [35, -20], [75, -55], [120, -90], [180, -115], [240, -120]],
28
+ "right arc": [[0, 0], [35, 20], [75, 55], [120, 90], [180, 115], [240, 120]],
29
+ "zigzag": [[0, 0], [45, -45], [90, 35], [135, -35], [180, 45], [230, 0]],
30
+ "circle": [[0, 0], [35, -55], [95, -75], [155, -45], [165, 20], [110, 55], [45, 45], [0, 0]],
31
+ }
32
+
33
+ runner = None
34
+
35
+
36
+ def ensure_drawmotion_code():
37
+ if not CODE_DIR.exists():
38
+ subprocess.run(["git", "clone", "--depth", "1", GIT_REPO, str(CODE_DIR)], check=True)
39
+ snapshot_download(
40
+ repo_id=MODEL_REPO,
41
+ repo_type="model",
42
+ allow_patterns=ASSET_PATTERNS,
43
+ local_dir=CODE_DIR,
44
+ )
45
+ if str(CODE_DIR) not in sys.path:
46
+ sys.path.insert(0, str(CODE_DIR))
47
+ os.chdir(CODE_DIR)
48
+
49
+
50
+ ensure_drawmotion_code()
51
+
52
+ from demo.drawmotion_studio.app import validate_generate_payload
53
+ from demo.drawmotion_studio.runner import DrawMotionRunner
54
+ from mogen.utils.plot_utils import plot_3d_motion, t2m_kinematic_chain
55
+
56
+
57
+ def get_runner():
58
+ global runner
59
+ if runner is None:
60
+ runner = DrawMotionRunner(
61
+ ckpt_path="logs/human_ml3d/last.ckpt",
62
+ gpu="0",
63
+ sample_index=0,
64
+ output_dir=str(ROOT / "runs"),
65
+ )
66
+ return runner
67
+
68
+
69
+ def normalize_custom_points(custom_points):
70
+ points = json.loads(custom_points)
71
+ normalized = []
72
+ for point in points:
73
+ if isinstance(point, dict):
74
+ normalized.append({"x": float(point["x"]), "y": float(point["y"])})
75
+ else:
76
+ normalized.append({"x": float(point[0]), "y": float(point[1])})
77
+ return normalized
78
+
79
+
80
+ def preset_points(name):
81
+ return [{"x": float(x), "y": float(y)} for x, y in EXAMPLES[name]]
82
+
83
+
84
+ def format_result_json(result):
85
+ slim = dict(result)
86
+ slim["pred_joint"] = np.asarray(slim["pred_joint"]).round(5).tolist()
87
+ slim["input_trajectory"] = np.asarray(slim["input_trajectory"]).round(5).tolist()
88
+ slim["pred_trajectory"] = np.asarray(slim["pred_trajectory"]).round(5).tolist()
89
+ return json.dumps(slim, indent=2)
90
+
91
+
92
+ @spaces.GPU(duration=300)
93
+ def generate(text, trajectory_mode, custom_trajectory, frames, alpha, trajectory_scale, ifg_repeat, ifg_scale):
94
+ if trajectory_mode == "custom JSON":
95
+ trajectory = normalize_custom_points(custom_trajectory)
96
+ else:
97
+ trajectory = preset_points(trajectory_mode)
98
+
99
+ payload = {
100
+ "text": text,
101
+ "trajectory": trajectory,
102
+ "length": int(frames),
103
+ "density": float(alpha),
104
+ "trajectory_scale": float(trajectory_scale),
105
+ "ifg_repeat": int(ifg_repeat),
106
+ "ifg_scale": float(ifg_scale),
107
+ "stickmen": [],
108
+ }
109
+ payload = validate_generate_payload(payload)
110
+ result = get_runner().generate(payload)
111
+
112
+ run_dir = sorted((ROOT / "runs").iterdir())[-1]
113
+ video_path = run_dir / "motion.mp4"
114
+ plot_3d_motion(
115
+ str(video_path),
116
+ t2m_kinematic_chain,
117
+ np.asarray(result["pred_joint"], dtype=np.float32),
118
+ title=result["text"],
119
+ fps=20,
120
+ )
121
+
122
+ result_json = format_result_json(result)
123
+ result_path = run_dir / "result_for_download.json"
124
+ result_path.write_text(result_json, encoding="utf-8")
125
+ return str(video_path), result_json, str(result_path)
126
+
127
+
128
+ def fill_custom_example(name):
129
+ if name == "custom JSON":
130
+ name = "left arc"
131
+ return json.dumps(EXAMPLES[name], indent=2)
132
+
133
+
134
+ with gr.Blocks(title="DrawMotion") as demo:
135
+ gr.Markdown("# DrawMotion")
136
+ gr.Markdown("Text and trajectory conditioned 3D human motion generation.")
137
+ with gr.Row():
138
+ with gr.Column(scale=1):
139
+ text = gr.Textbox(
140
+ label="Text",
141
+ value="A person walks forward and turns left.",
142
+ lines=2,
143
+ )
144
+ trajectory_mode = gr.Dropdown(
145
+ choices=list(EXAMPLES.keys()) + ["custom JSON"],
146
+ value="left arc",
147
+ label="Trajectory",
148
+ )
149
+ custom_trajectory = gr.Textbox(
150
+ label="Custom trajectory JSON",
151
+ value=fill_custom_example("left arc"),
152
+ lines=8,
153
+ )
154
+ with gr.Row():
155
+ frames = gr.Slider(32, 196, value=120, step=1, label="Frames")
156
+ alpha = gr.Slider(0, 1, value=0.2, step=0.05, label="Alpha")
157
+ with gr.Row():
158
+ trajectory_scale = gr.Slider(20, 200, value=50, step=1, label="Trajectory scale")
159
+ ifg_repeat = gr.Slider(0, 100, value=50, step=1, label="IFG repeat")
160
+ ifg_scale = gr.Slider(0, 200, value=50, step=1, label="IFG scale")
161
+ run_button = gr.Button("Generate", variant="primary")
162
+ with gr.Column(scale=1):
163
+ video = gr.Video(label="Generated motion")
164
+ result_json = gr.Code(label="Result JSON", language="json", lines=18)
165
+ result_file = gr.File(label="Download result.json")
166
+
167
+ trajectory_mode.change(
168
+ fn=fill_custom_example,
169
+ inputs=trajectory_mode,
170
+ outputs=custom_trajectory,
171
+ show_progress="hidden",
172
+ )
173
+ run_button.click(
174
+ fn=generate,
175
+ inputs=[text, trajectory_mode, custom_trajectory, frames, alpha, trajectory_scale, ifg_repeat, ifg_scale],
176
+ outputs=[video, result_json, result_file],
177
+ concurrency_limit=1,
178
+ )
179
+
180
+ demo.queue(max_size=8).launch()
packages.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ ffmpeg
2
+ git
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ spaces
2
+ gradio==5.49.1
3
+ huggingface_hub
4
+ git+https://github.com/openai/CLIP.git
5
+ torch==2.8.0
6
+ torchvision==0.23.0
7
+ mmcv==1.7.2
8
+ lightning==2.3.3
9
+ einops==0.8.1
10
+ matplotlib==3.10.3
11
+ numpy==1.26.4
12
+ opencv-python-headless==4.10.0.84
13
+ packaging==25.0
14
+ Pillow==11.2.1
15
+ scipy==1.15.3
16
+ tqdm==4.66.4