Spaces:
Runtime error
Runtime error
File size: 9,861 Bytes
a8e08c2 548c933 3dd1e63 548c933 a8e08c2 548c933 3dd1e63 548c933 a8e08c2 548c933 3dd1e63 548c933 a8e08c2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | """Two CPU text-to-motion models in one Space, one tab each.
Import order matters here and is not cosmetic. matplotlib's ft2font is a
pybind11 extension built against the system libstdc++ and fails to initialise if
a differently linked libstdc++ is already resident, so the drawing stack is
imported before anything native. Kimodo then dlopens libkimodo.so together with
its own bundled libgomp at generate time, while MoMask's torch, onnxruntime and
CLIP are held back behind momask_core._ensure_backends() until that tab is
actually used. A visitor who only generates with Kimodo never loads them.
Each pipeline keeps its own weights. Kimodo runs GGUF checkpoints through the
kimodo.cpp GGML port; MoMask runs its own ONNX graphs. Nothing is shared or
substituted between them.
"""
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt # noqa: E402,F401
import gradio as gr # noqa: E402
import kimodo_core as K # noqa: E402
import momask_core as M # noqa: E402
CSS = K.CSS + """
#mm_log textarea { font-family: ui-monospace, monospace; font-size: 0.8em; }
.tabs { gap: 0 !important; }
"""
HEADER = (f"CPU text to motion · **Kimodo** through "
f"[kimodo.cpp](https://github.com/localai-org/kimodo.cpp) GGML, "
f"**MoMask** through ONNX · {K.CPUS} threads")
def momask_generate(text, length, seed):
"""Generate a 3D skeletal animation from a text description of a motion.
Runs MoMask on CPU through ONNX Runtime. Returns a rendered preview and a
BVH file for Blender. Far cheaper than the Kimodo tab because there is no
LLM2Vec encode: CLIP text encoding is part of the graph.
Args:
text: Plain English description of the motion, for example "a person walks forward".
length: Clip length in seconds. 0 asks the length estimator to pick one.
seed: Random seed. The same seed and text reproduce a clip exactly.
"""
if not (text or "").strip():
raise gr.Error("Describe a motion first, for example "
"\"a person walks forward\".")
_joints, video_path, bvh_path = M.generate_motion(
text, float(length or 0), int(seed) if seed is not None else None)
if video_path is None:
raise gr.Error("MoMask returned no motion. Try a shorter, plainer "
"description, or set Duration above 0.")
return video_path, gr.update(value=bvh_path, interactive=bvh_path is not None)
def kimodo_example(prompt, model_label, frames, seed):
"""Examples vary the four columns worth varying; the advanced controls keep
their defaults. Delegating to K.generate rather than duplicating it means a
cached example is the same code path the Generate button runs.
The BVH button is dropped from the yielded tuple on purpose. Caching an
example whose outputs include a gr.DownloadButton crashes inside gradio:
postprocess_data reaches components/base.py `if self.info:` and
DownloadButton has no `info` attribute. Video, skeleton and log cache fine,
and the button still works normally from Generate.
"""
for video, glb, _bvh, status in K.generate(
prompt, model_label, int(frames), 20, int(seed), 2.0, True):
yield video, glb, status
def momask_example(text, length, seed):
"""Same reason as above: cache the preview, leave the download button out."""
video, _bvh = momask_generate(text, length, seed)
return video
def kimodo_tab():
"""Kimodo's own layout, unchanged apart from living inside a tab."""
with gr.Row():
with gr.Column(scale=4):
prompt = gr.Textbox(
value="a person runs forward and jumps over an obstacle",
label="Describe the motion", lines=1, max_lines=1,
placeholder="a person performs a spinning back kick")
run = gr.Button(
f"Generate ({K._duration(K.ENCODE_S + K.sampling_seconds(120, 20))})",
variant="primary")
status = gr.Textbox(lines=8, max_lines=K.LOG_LINES + 2,
autoscroll=True, show_label=False,
container=False, elem_id="log")
with gr.Column(scale=3):
model_label = gr.Dropdown(
choices=[c.label for c in K.CHECKPOINTS],
value=K.CHECKPOINTS[0].label, label="Motion model",
info="SOMA is a 30 joint human rig, G1 a 34 joint Unitree "
"robot. RP and SEED are different checkpoints of each.")
frames = gr.Slider(30, 240, value=120, step=10,
label=f"Frames ({120 / K.FPS:.1f} s clip)")
with gr.Accordion("Advanced", open=False):
with gr.Row():
steps = gr.Slider(4, 50, value=20, step=1, scale=3,
label="Diffusion steps (20)",
info="More steps, cleaner motion.")
seed = gr.Number(value=7, precision=0, label="Seed", scale=1)
with gr.Row():
text_cfg = gr.Slider(1.0, 5.0, value=2.0, step=0.5, scale=3,
label="Guidance")
deskate = gr.Checkbox(value=True, scale=1,
label="Reduce foot skating")
with gr.Column(scale=5):
viewer = gr.Model3D(label="Skeleton", interactive=False, height=260)
video = gr.Video(label="Preview", autoplay=True, height=180)
bvh = gr.DownloadButton("Download BVH for Blender", size="sm",
interactive=False)
# Defined after the row because Examples needs every input component to exist.
gr.Examples(
examples=[
["a person walks forward at a steady pace", K.CHECKPOINTS[0].label, 120, 42],
["a person performs a roundhouse kick", K.CHECKPOINTS[0].label, 120, 123],
["a person sits down on a chair", K.CHECKPOINTS[0].label, 90, 456],
["a person dances happily in place", K.CHECKPOINTS[0].label, 150, 789],
["a person kicks with their right leg", K.CHECKPOINTS[2].label, 120, 101],
],
inputs=[prompt, model_label, frames, seed],
outputs=[video, viewer, status],
fn=kimodo_example,
# Lazy is the only sane mode here. Each distinct prompt pays a ~110 s
# LLM2Vec encode, so eager would add roughly ten minutes to every build
# and every wake, and one failure would take the whole Space down with it.
# Lazy caches a row the first time someone clicks it, instantly after.
cache_examples=True, cache_mode="lazy",
label="Examples")
for control in (prompt, frames, steps):
control.change(K.control_labels, inputs=[prompt, frames, steps],
outputs=[frames, steps, run], queue=False,
api_visibility="private")
run.click(K.generate,
inputs=[prompt, model_label, frames, steps, seed, text_cfg, deskate],
outputs=[video, viewer, bvh, status],
api_name="generate").then(
K.control_labels, inputs=[prompt, frames, steps],
outputs=[frames, steps, run], queue=False, api_visibility="private")
def momask_tab():
"""Same shape as the Kimodo tab so switching tabs does not relayout the page:
inputs left, settings middle, outputs right."""
with gr.Row():
with gr.Column(scale=4):
text = gr.Textbox(value="a person walks forward",
label="Describe the motion", lines=1, max_lines=1,
placeholder="a person does a salsa dance")
run = gr.Button("Generate (~10 s)", variant="primary")
with gr.Column(scale=3):
length = gr.Slider(0, 10, value=0, step=0.5,
label="Duration (0 = estimate it)",
info="Seconds. The length estimator picks one "
"from the text when this is 0.")
seed = gr.Number(value=42, precision=0, label="Seed")
with gr.Column(scale=5):
video = gr.Video(label="Preview", autoplay=True, height=260)
bvh = gr.DownloadButton("Download BVH for Blender", size="sm",
interactive=False)
# Defined after the row because Examples needs every input component to exist.
gr.Examples(
examples=[
["A person walks forward", 0, 42],
["A person is running on a treadmill", 0, 123],
["A person jumps up and then lands", 0, 456],
["A person does a salsa dance", 0, 789],
["A person kicks with their right leg", 0, 101],
],
inputs=[text, length, seed], outputs=[video],
fn=momask_example,
# Lazy, not eager. Eager runs every row before the Space serves its first
# request, and it would force torch, onnxruntime and CLIP resident at
# startup for every visitor, which is exactly what _ensure_backends()
# exists to avoid. Lazy caches each row the first time it is clicked and
# serves it instantly to everyone after that.
cache_examples=True, cache_mode="lazy",
label="Examples")
run.click(momask_generate, inputs=[text, length, seed],
outputs=[video, bvh], api_name="momask_generate")
with gr.Blocks(title="Kimodo cpp + MoMask", fill_width=True) as demo:
gr.Markdown(HEADER, elem_id="foot")
with gr.Tabs():
with gr.Tab("Kimodo (GGML)"):
kimodo_tab()
with gr.Tab("MoMask (ONNX)"):
momask_tab()
if __name__ == "__main__":
demo.queue(max_size=8, default_concurrency_limit=1).launch(
css=CSS, mcp_server=True)
|