3D Visualization & Replay Studio

The padelkit.viz module provides an interactive Three.js 3D court visualizer, multi-camera inspector, and synthetic pass viewer (RGB, Semantic Segmentation, Depth, and 17 Court Landmarks) powered by a unified temporal state contract (MatchTimeline).

Operating Modes

  • Name
    1. CSV / Timeline Replay Mode
    Type
    MatchTimeline.from_csv()
    Description

    Load any saved match or rally CSV file and scrub through time at 0.25x–2.0x speed, switch between Camera presets, or drag-and-drop a .csv file directly into the browser window.

  • Name
    2. Real-Time Live Streaming Mode
    Type
    visualizer.serve()
    Description

    Launch a local HTTP + Server-Sent Events (SSE) server and push FrameState objects in real time from a Python simulation loop or live tracking pipeline using visualizer.push_frame(frame).

  • Name
    3. Standalone HTML & Jupyter Mode
    Type
    visualizer.export_html()
    Description

    Export a self-contained .html file that runs in any browser with zero server dependencies, or render the 3D studio directly inside a Jupyter / Google Colab notebook cell.


Quickstart: Generate a Rally CSV & Launch the 3D Studio

MatchTimeline.demo_rally() generates a complete 60 FPS 3D rally with realistic ball parabolas, floor and glass-wall rebounds, and 4 articulated players moving and swinging rackets. You can export it to CSV and inspect it in 3D immediately.

CSV Replay & HTML Export

guide
PadelVisualizer
from padelkit import Court, MatchTimeline, PadelVisualizer

court = Court(
    dimensions=Court.fip_standard().dimensions,
    turf_color="blue",
)

# 1. Generate a 3D rally timeline and save it to CSV
timeline = MatchTimeline.demo_rally(court=court, fps=60.0)
timeline.to_csv("demo_rally.csv")

# 2. Load the CSV into the Three.js 3D Visualizer
#    (optionally pass animation_library_dir for Mixamo .glb strokes)
viz = PadelVisualizer.from_csv(
    "demo_rally.csv",
    court=court,
    animation_library_dir="../padelkit-animator/data/animations",
)

# 3. Export standalone HTML or launch interactive server
viz.export_html("padel_studio.html")
# viz.serve(port=8765, open_browser=True)

methodvisualizer.push_frame()

Real-Time Streaming Mode

When running viz.serve(background=True), your Python code can stream live FrameState updates to all connected browser tabs in real time.

FrameState Properties

  • Name
    timestamp
    Type
    float
    Description

    Elapsed time in seconds.

  • Name
    ball_position
    Type
    tuple[float, float, float]
    Description

    3D ball coordinates (X, Y, Z) in meters.

  • Name
    ball_velocity
    Type
    tuple[float, float, float]
    Description

    3D linear ball velocity (vx, vy, vz) in m/s. Exposes .ball_speed_kmh.

  • Name
    ball_spin
    Type
    tuple[float, float, float]
    Description

    3D angular velocity (wx, wy, wz) in rad/s. Exposes .ball_spin_rpm and drives 3D ball seam rotation in the studio.

  • Name
    players
    Type
    dict[str, PlayerPose]
    Description

    Dictionary mapping "P1".."P4" to their PlayerPose(player_id, x, y, z, yaw_deg, animation, team, swing_phase).

  • Name
    score
    Type
    str
    Description

    Formatted match score string displayed on the 3D HUD.

  • Name
    event
    Type
    str | None
    Description

    Optional event tag ("serve", "bounce_floor", "bounce_glass", "shot_smash").

Live Streaming

method
push_frame
import time
from padelkit import PadelVisualizer, FrameState, PlayerPose

viz = PadelVisualizer()
viz.serve(port=8765, open_browser=True, background=True)

for step in range(120):
    t = step / 60.0
    viz.push_frame(
        FrameState(
            timestamp=t,
            ball_position=(0.0, -5.0 + 5.0 * t, 1.2),
            players={
                "P1": PlayerPose("P1", x=1.8, y=-6.5, yaw_deg=0.0, team="A"),
                "P2": PlayerPose("P2", x=-2.0, y=-3.0, yaw_deg=0.0, team="A"),
                "P3": PlayerPose("P3", x=-2.2, y=7.0, yaw_deg=180.0, team="B"),
                "P4": PlayerPose("P4", x=2.2, y=7.0, yaw_deg=180.0, team="B"),
            },
            score="0-0 | 15-0 *",
            event="live_tracking",
        )
    )
    time.sleep(1 / 60)

Was this page helpful?