microduck / app.py
kyoungsim's picture
Upload MicroDuck RL policies and MuJoCo simulation assets
80dc52c verified
Raw History Blame Contribute Delete
7.53 kB
import os
import tempfile
import numpy as np
import imageio
import gradio as gr
import mujoco
from infer_engine import SpacePolicyInference
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
ROBOT_DIR = os.path.join(CURRENT_DIR, "robot")
POLICIES_DIR = os.path.join(CURRENT_DIR, "policies")
def simulate_motion(motion_type, duration, camera_angle, progress=gr.Progress()):
progress(0, desc="์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ™˜๊ฒฝ ์ดˆ๊ธฐํ™” ์ค‘...")
# Select XML model
if motion_type == "โšฝ ์™ผ๋ฐœ ๊ณต์ฐจ๊ธฐ (Ball Kick)":
xml_path = os.path.join(ROBOT_DIR, "scene_ball.xml")
else:
xml_path = os.path.join(ROBOT_DIR, "scene.xml")
model = mujoco.MjModel.from_xml_path(xml_path)
model.opt.timestep = 0.005
data = mujoco.MjData(model)
# Initialize policy
engine = SpacePolicyInference(model, data, POLICIES_DIR)
# Reset robot position to standing default
freejoint_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_JOINT, "trunk_base_freejoint")
qpos_adr = model.jnt_qposadr[freejoint_id]
data.qpos[qpos_adr + 0] = 0.0
data.qpos[qpos_adr + 1] = 0.0
data.qpos[qpos_adr + 2] = 0.125
data.qpos[qpos_adr + 3:qpos_adr + 7] = [1, 0, 0, 0]
for i, idx in enumerate(engine.joint_qpos_indices):
data.qpos[idx] = engine.default_pose[i]
data.ctrl[:] = engine.default_pose
mujoco.mj_forward(model, data)
# Configure behavior
if motion_type == "๐Ÿšถ ์ „์ง„ ๊ฑท๊ธฐ (Forward Walk)":
engine.set_vel_cmd(0.25, 0.0, 0.0)
elif motion_type == "๐Ÿšถ ํ›„์ง„ ๊ฑท๊ธฐ (Backward Walk)":
engine.set_vel_cmd(-0.2, 0.0, 0.0)
elif motion_type == "๐Ÿ”„ ์ขŒํšŒ์ „ (Turn Left)":
engine.set_vel_cmd(0.1, 0.0, 0.8)
elif motion_type == "๐Ÿ”„ ์šฐํšŒ์ „ (Turn Right)":
engine.set_vel_cmd(0.1, 0.0, -0.8)
elif motion_type == "๐Ÿง ์ œ์ž๋ฆฌ ์„œ๊ธฐ (Standing Balance)":
engine.set_vel_cmd(0.0, 0.0, 0.0)
elif motion_type == "๐Ÿคธ ์•ž๊ตฌ๋ฅด๊ธฐ (Roulade)":
engine.trigger_behavior("roulade")
elif motion_type == "โšฝ ์™ผ๋ฐœ ๊ณต์ฐจ๊ธฐ (Ball Kick)":
engine.trigger_behavior("kick_left")
elif motion_type == "๐ŸงŽ ๋ฐ”๋‹ฅ ์ค๊ธฐ (Ground Pick)":
engine.trigger_ground_pick()
elif motion_type == "๐Ÿช‘ ์•‰๊ธฐ (Sit Down)":
engine.trigger_sit()
# Offscreen renderer
width, height = 640, 480
renderer = mujoco.Renderer(model, height, width)
# Setup camera
camera = mujoco.MjvCamera()
mujoco.mjv_defaultCamera(camera)
frames = []
fps = 30
control_freq = 50 # Hz
decimation = 4
total_steps = int(duration * control_freq)
dt = 1.0 / control_freq
# Render frequency (every ~1.66 control steps to reach 30 fps)
sim_time = 0.0
next_render_time = 0.0
for step_i in range(total_steps):
engine.update(dt)
engine.step()
for _ in range(decimation):
mujoco.mj_step(model, data)
sim_time += dt
if sim_time >= next_render_time:
# Update camera to track robot
robot_pos = data.qpos[qpos_adr:qpos_adr + 3]
camera.lookat = [robot_pos[0], robot_pos[1], robot_pos[2] + 0.05]
if camera_angle == "์ธก๋ฉด ์ถ”์  (Side View)":
camera.distance = 1.1
camera.elevation = -15
camera.azimuth = 90
elif camera_angle == "์ •๋ฉด ๋ทฐ (Front View)":
camera.distance = 1.0
camera.elevation = -10
camera.azimuth = 180
elif camera_angle == "๋Œ€๊ฐ์„  ๋ทฐ (Isometric)":
camera.distance = 1.2
camera.elevation = -22
camera.azimuth = 135
elif camera_angle == "ํ›„๋ฐฉ 3์ธ์นญ (Behind View)":
camera.distance = 1.1
camera.elevation = -12
camera.azimuth = 0
renderer.update_scene(data, camera=camera)
img = renderer.render()
frames.append(img)
next_render_time += 1.0 / fps
if step_i % 15 == 0:
progress(step_i / total_steps, desc=f"์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ง„ํ–‰ ์ค‘... ({step_i}/{total_steps})")
progress(0.95, desc="๋น„๋””์˜ค ํŒŒ์ผ ์ƒ์„ฑ ์ค‘...")
temp_dir = tempfile.mkdtemp()
video_path = os.path.join(temp_dir, "output.mp4")
imageio.mimsave(video_path, frames, fps=fps)
final_pos = data.qpos[qpos_adr:qpos_adr + 3]
distance = np.linalg.norm(final_pos[:2])
status_md = f"""
### ๐Ÿ“Š ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒฐ๊ณผ ์š”์•ฝ
- **์„ ํƒ๋œ ๋™์ž‘**: `{motion_type}`
- **์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹œ๊ฐ„**: `{duration:.1f}์ดˆ` (์ด {len(frames)} ํ”„๋ ˆ์ž„ / 30 FPS)
- **์ตœ์ข… ์ด๋™ ๊ฑฐ๋ฆฌ**: `{distance:.3f} m` (X: `{final_pos[0]:.2f}m`, Y: `{final_pos[1]:.2f}m`, Z: `{final_pos[2]:.2f}m`)
- **๋กœ๋ด‡ ์ƒํƒœ**: `์ •์ƒ ์•ˆ์ • ์ƒํƒœ ์œ ์ง€ ์™„๋ฃŒ`
"""
return video_path, status_md
# Gradio Interface
custom_css = """
#container { max-width: 960px; margin: 0 auto; }
.gr-button-primary { background: linear-gradient(90deg, #F59E0B 0%, #D97706 100%) !important; border: none !important; }
"""
with gr.Blocks(title="๐Ÿฆ† MicroDuck 3D Robot Simulator", css=custom_css, theme=gr.themes.Soft()) as demo:
gr.Markdown(
"""
# ๐Ÿฆ† MicroDuck 3D ๋กœ๋ด‡ ๊ฐ•ํ™”ํ•™์Šต ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ
MuJoCo ๋ฌผ๋ฆฌ ์—”์ง„๊ณผ ์˜จ๋‹‰์Šค(ONNX) ์‹ฌ์ธต๊ฐ•ํ™”ํ•™์Šต(Deep RL) ์ •์ฑ… ๋ชจ๋ธ๋กœ ๊ตฌ๋™๋˜๋Š” **MicroDuck** ๋กœ๋ด‡์˜ ๋™์ž‘์„ ์›น์—์„œ ์ง์ ‘ ์‹คํ–‰ํ•˜๊ณ  ๊ด€์ฐฐํ•ด๋ณด์„ธ์š”.
"""
)
with gr.Row():
with gr.Column(scale=4):
motion_input = gr.Radio(
label="๐ŸŽฏ ๋กœ๋ด‡ ๋™์ž‘ (Motion Policy)",
choices=[
"๐Ÿšถ ์ „์ง„ ๊ฑท๊ธฐ (Forward Walk)",
"๐Ÿšถ ํ›„์ง„ ๊ฑท๊ธฐ (Backward Walk)",
"๐Ÿ”„ ์ขŒํšŒ์ „ (Turn Left)",
"๐Ÿ”„ ์šฐํšŒ์ „ (Turn Right)",
"๐Ÿง ์ œ์ž๋ฆฌ ์„œ๊ธฐ (Standing Balance)",
"๐Ÿคธ ์•ž๊ตฌ๋ฅด๊ธฐ (Roulade)",
"โšฝ ์™ผ๋ฐœ ๊ณต์ฐจ๊ธฐ (Ball Kick)",
"๐ŸงŽ ๋ฐ”๋‹ฅ ์ค๊ธฐ (Ground Pick)",
"๐Ÿช‘ ์•‰๊ธฐ (Sit Down)",
],
value="๐Ÿšถ ์ „์ง„ ๊ฑท๊ธฐ (Forward Walk)"
)
duration_input = gr.Slider(
label="โฑ๏ธ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์‹œ๊ฐ„ (์ดˆ)",
minimum=1.5,
maximum=4.5,
value=2.5,
step=0.5
)
camera_input = gr.Dropdown(
label="๐ŸŽฅ ์นด๋ฉ”๋ผ ์•ต๊ธ€",
choices=["์ธก๋ฉด ์ถ”์  (Side View)", "์ •๋ฉด ๋ทฐ (Front View)", "๋Œ€๊ฐ์„  ๋ทฐ (Isometric)", "ํ›„๋ฐฉ 3์ธ์นญ (Behind View)"],
value="๋Œ€๊ฐ์„  ๋ทฐ (Isometric)"
)
run_btn = gr.Button("๐ŸŽฌ 3D ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ Œ๋”๋ง ์‹œ์ž‘", variant="primary", size="lg")
with gr.Column(scale=6):
video_output = gr.Video(label="๐Ÿ“น ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋ Œ๋”๋ง ์˜์ƒ", autoplay=True, loop=True)
status_output = gr.Markdown("์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•˜๋ฉด ๊ฒฐ๊ณผ ํ†ต๊ณ„๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.")
run_btn.click(
fn=simulate_motion,
inputs=[motion_input, duration_input, camera_input],
outputs=[video_output, status_output]
)
gr.Markdown(
"""
---
๐Ÿ’ก **MicroDuck ๊ธฐ์ˆ  ์Šคํƒ**: MuJoCo Physics, ONNX Runtime, Isaac Gym / Genesis RL Trained Policies.
"""
)
if __name__ == "__main__":
demo.launch()