|
|
import gradio as gr |
|
|
import numpy as np |
|
|
from robot import TrajectoryPlanner |
|
|
import logging |
|
|
import plotly.graph_objects as go |
|
|
from plotly.subplots import make_subplots |
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") |
|
|
import time |
|
|
import threading |
|
|
|
|
|
|
|
|
try: |
|
|
planner = TrajectoryPlanner() |
|
|
PLANNER_AVAILABLE = True |
|
|
except Exception as e: |
|
|
logging.error(f"Failed to initialize trajectory planner: {e}") |
|
|
PLANNER_AVAILABLE = False |
|
|
|
|
|
animation_running = False |
|
|
animation_thread = None |
|
|
|
|
|
css = """ |
|
|
.control-panel .gr-slider { |
|
|
margin-top: 4px !important; |
|
|
margin-bottom: 4px !important; |
|
|
} |
|
|
.control-panel .gr-form { |
|
|
gap: 6px !important; |
|
|
} |
|
|
.label-override { |
|
|
font-weight: 500 !important; |
|
|
font-size: 1.1em !important; |
|
|
color: #333 !important; |
|
|
} |
|
|
""" |
|
|
|
|
|
def create_comparison_plots(plot_data): |
|
|
"""Create comparison plots for the trajectories""" |
|
|
if plot_data is None: |
|
|
return None |
|
|
|
|
|
ours = plot_data['ours'] |
|
|
toppra = plot_data['toppra'] |
|
|
|
|
|
|
|
|
fig = make_subplots( |
|
|
rows=3, cols=2, |
|
|
subplot_titles=('Position', 'Velocity', 'Acceleration', 'Jerk', 'Position Error', 'Orientation Error'), |
|
|
vertical_spacing=0.12, |
|
|
horizontal_spacing=0.1 |
|
|
) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['x'], name='Ours', |
|
|
line=dict(color='#6C8EBF', width=2), showlegend=True), row=1, col=1) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['x'], name='TOPPRA', |
|
|
line=dict(color='#B85450', width=2), showlegend=True), row=1, col=1) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['dx'], name='Ours', |
|
|
line=dict(color='#6C8EBF', width=2), showlegend=False), row=1, col=2) |
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['dx_max'], name='Ours Limits', |
|
|
line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=True), row=1, col=2) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['dx'], name='TOPPRA', |
|
|
line=dict(color='#B85450', width=2), showlegend=False), row=1, col=2) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['ds_max'], name='TOPPRA Limits', |
|
|
line=dict(color='#B85450', width=1, dash='dash'), showlegend=True), row=1, col=2) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['ddx'], name='Ours', |
|
|
line=dict(color='#6C8EBF', width=2), showlegend=False), row=2, col=1) |
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['ddx_max'], name='Ours Limits', |
|
|
line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=False), row=2, col=1) |
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['ddx_min'], name='Ours Limits', |
|
|
line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=False), row=2, col=1) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['ddx'], name='TOPPRA', |
|
|
line=dict(color='#B85450', width=2), showlegend=False), row=2, col=1) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['dds_max'], name='TOPPRA Limits', |
|
|
line=dict(color='#B85450', width=1, dash='dash'), showlegend=False), row=2, col=1) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['dds_min'], name='TOPPRA Limits', |
|
|
line=dict(color='#B85450', width=1, dash='dash'), showlegend=False), row=2, col=1) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['dddx'], name='Ours', |
|
|
line=dict(color='#6C8EBF', width=2), showlegend=False), row=2, col=2) |
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['dddx_max'], name='Ours Limits', |
|
|
line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=False), row=2, col=2) |
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['dddx_min'], name='Ours Limits', |
|
|
line=dict(color='#6C8EBF', width=1, dash='dash'), showlegend=False), row=2, col=2) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['dddx'], name='TOPPRA', |
|
|
line=dict(color='#B85450', width=2), showlegend=False), row=2, col=2) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['ddds_max'], name='TOPPRA Limits', |
|
|
line=dict(color='#B85450', width=1, dash='dash'), showlegend=False), row=2, col=2) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['ddds_min'], name='TOPPRA Limits', |
|
|
line=dict(color='#B85450', width=1, dash='dash'), showlegend=False), row=2, col=2) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['e_pos'], name='Ours', |
|
|
line=dict(color='#6C8EBF', width=2), showlegend=False), row=3, col=1) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['e_pos'], name='TOPPRA', |
|
|
line=dict(color='#B85450', width=2), showlegend=False), row=3, col=1) |
|
|
|
|
|
|
|
|
fig.add_trace(go.Scatter(x=ours['t'], y=ours['e_rot'], name='Ours', |
|
|
line=dict(color='#6C8EBF', width=2), showlegend=False), row=3, col=2) |
|
|
fig.add_trace(go.Scatter(x=toppra['t'], y=toppra['e_rot'], name='TOPPRA', |
|
|
line=dict(color='#B85450', width=2), showlegend=False), row=3, col=2) |
|
|
|
|
|
|
|
|
|
|
|
fig.update_xaxes(title_text="Time [s]", row=3, col=1) |
|
|
fig.update_xaxes(title_text="Time [s]", row=3, col=2) |
|
|
|
|
|
fig.update_yaxes(title_text="s [m]", row=1, col=1) |
|
|
fig.update_yaxes(title_text="ds [m/s]", row=1, col=2) |
|
|
fig.update_yaxes(title_text="dds [m/s²]", row=2, col=1) |
|
|
fig.update_yaxes(title_text="ddds [m/s³]", row=2, col=2) |
|
|
fig.update_yaxes(title_text="Position Error [mm]", row=3, col=1) |
|
|
fig.update_yaxes(title_text="Orientation Error [deg]", row=3, col=2) |
|
|
|
|
|
fig.update_layout(height=600, showlegend=True, legend=dict(x=0.5, y=1.1, orientation='h')) |
|
|
|
|
|
return fig |
|
|
|
|
|
def animate_trajectory(): |
|
|
"""Background thread to animate the trajectory""" |
|
|
global animation_running |
|
|
|
|
|
anim_data = planner.get_animation_data() |
|
|
if anim_data is None: |
|
|
return |
|
|
|
|
|
t_max = anim_data['t_max'] |
|
|
|
|
|
for i in range(10): |
|
|
if not animation_running: |
|
|
return |
|
|
t0 = time.time() |
|
|
|
|
|
while animation_running and (time.time() - t0) < t_max: |
|
|
t_current = time.time() - t0 |
|
|
planner.update_animation(t_current) |
|
|
time.sleep(0.01) |
|
|
|
|
|
animation_running = False |
|
|
|
|
|
|
|
|
def generate_and_compute(traj_length, capacity_scale, progress=gr.Progress()): |
|
|
"""Generate trajectory and compute both algorithms""" |
|
|
if not PLANNER_AVAILABLE: |
|
|
return "Planner not available", None, None |
|
|
|
|
|
progress(0, desc="Generating trajectory...") |
|
|
|
|
|
|
|
|
result = planner.generate_trajectory(traj_length, capacity_scale, progress) |
|
|
|
|
|
info = f""" |
|
|
✅ **Trajectory Generated Successfully** |
|
|
|
|
|
- **Waypoints**: {result['waypoints']} |
|
|
- **Trajectory Length**: {traj_length:.2f} m |
|
|
- **Capacity Scale**: {capacity_scale:.2f} |
|
|
- **TOPPRA Duration**: {result['toppra_duration']:.3f} s |
|
|
- **Ours Duration**: {result['ours_duration']:.3f} s |
|
|
- **Speedup**: {(result['toppra_duration'] / result['ours_duration']):.2f}x |
|
|
""" |
|
|
|
|
|
progress(0.8, desc="Creating plots...") |
|
|
|
|
|
plot_data = planner.get_plot_data() |
|
|
plots = create_comparison_plots(plot_data) |
|
|
|
|
|
progress(1.0, desc="Done!") |
|
|
|
|
|
return info, plots, gr.update(interactive=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def start_animation(): |
|
|
"""Start the animation""" |
|
|
global animation_running, animation_thread |
|
|
|
|
|
if not PLANNER_AVAILABLE or planner.data is None: |
|
|
return gr.update(value="⚠️ Generate trajectory first") |
|
|
|
|
|
animation_running = True |
|
|
animation_thread = threading.Thread(target=animate_trajectory, daemon=True) |
|
|
animation_thread.start() |
|
|
|
|
|
return gr.update(value="⏸️ Stop Animation", variant="stop") |
|
|
|
|
|
def stop_animation(): |
|
|
"""Stop the animation""" |
|
|
global animation_running |
|
|
animation_running = False |
|
|
|
|
|
return gr.update(value="▶️ Play Animation", variant="primary") |
|
|
|
|
|
def toggle_animation(btn_state): |
|
|
"""Toggle animation on/off""" |
|
|
if "Stop" in btn_state: |
|
|
return stop_animation() |
|
|
else: |
|
|
return start_animation() |
|
|
|
|
|
with gr.Blocks(css=css, title="Capacity-Aware Trajectory Planning") as demo: |
|
|
|
|
|
gr.Markdown(""" |
|
|
# 🤖 Capacity-Aware Trajectory Planning Comparison |
|
|
|
|
|
📄 This demo implements the paper: **Online approach to near time-optimal task-space trajectory planning** |
|
|
by Antun Skuric, Nicolas Torres Alberto, Lucas Joseph, Vincent Padois, David Daney |
|
|
Link: https://inria.hal.science/hal-04576076 |
|
|
|
|
|
|
|
|
|
|
|
Compare **capacity-aware real-time trajectory planning** vs **TOPPRA** for the Panda robot. |
|
|
|
|
|
**Left Robot**: Our approach | **Right Robot**: TOPPRA |
|
|
""") |
|
|
|
|
|
with gr.Row(equal_height=True): |
|
|
|
|
|
with gr.Column(scale=2, min_width=500): |
|
|
if PLANNER_AVAILABLE: |
|
|
viewer = gr.HTML(planner.iframe(height=600)) |
|
|
else: |
|
|
viewer = gr.HTML("<div style='height:600px; background:#f0f0f0; display:flex; align-items:center; justify-content:center;'><h2>Planner Not Available</h2></div>") |
|
|
|
|
|
|
|
|
with gr.Column(scale=1, min_width=400, elem_classes="control-panel"): |
|
|
gr.Markdown("## 🎛️ Trajectory Configuration") |
|
|
|
|
|
traj_length = gr.Slider( |
|
|
0.1, 1.0, value=0.8, step=0.05, |
|
|
label="Trajectory Length (m)", |
|
|
info="Distance between start and end points" |
|
|
) |
|
|
|
|
|
capacity_scale = gr.Slider( |
|
|
0.1, 1.0, value=0.5, step=0.05, |
|
|
label="Robot Capacity Scale", |
|
|
info="Fraction of maximum robot capacity (1.0 = full capacity)" |
|
|
) |
|
|
|
|
|
generate_btn = gr.Button("🎲 Generate Random Trajectory", variant="primary", size="lg") |
|
|
|
|
|
info_box = gr.Markdown("Click 'Generate' to create a random trajectory") |
|
|
|
|
|
gr.Markdown("## 🎬 Animation Control") |
|
|
|
|
|
play_btn = gr.Button("▶️ Play Animation", variant="primary", interactive=False) |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
gr.Markdown("## 📊 Trajectory Comparison") |
|
|
plots = gr.Plot(label="Comparison Plots") |
|
|
|
|
|
|
|
|
generate_btn.click( |
|
|
generate_and_compute, |
|
|
inputs=[traj_length, capacity_scale], |
|
|
outputs=[info_box, plots, play_btn] |
|
|
) |
|
|
|
|
|
play_btn.click( |
|
|
toggle_animation, |
|
|
inputs=[play_btn], |
|
|
outputs=[play_btn] |
|
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch(server_name="0.0.0.0", server_port=8501) |
|
|
|