Skip to content

server

Viser server setup utilities.

compute_grid_size(pos: np.ndarray | None, padding: float = 1.2, default_size: float = 10.0) -> float

Compute grid size based on trajectory extent.

Parameters:

Name Type Description Default
pos ndarray | None

Position array of shape (N, 3), or None if position is not available

required
padding float

Padding factor (1.2 = 20% padding)

1.2
default_size float

Grid size when pos is None

10.0

Returns:

Type Description
float

Grid size (width and height)

Source code in openscvx/plotting/viser/server.py
def compute_grid_size(
    pos: np.ndarray | None,
    padding: float = 1.2,
    default_size: float = 10.0,
) -> float:
    """Compute grid size based on trajectory extent.

    Args:
        pos: Position array of shape (N, 3), or None if position is not available
        padding: Padding factor (1.2 = 20% padding)
        default_size: Grid size when pos is None

    Returns:
        Grid size (width and height)
    """
    if pos is None:
        return default_size
    max_x = np.abs(pos[:, 0]).max()
    max_y = np.abs(pos[:, 1]).max()
    return max(max_x, max_y) * 2 * padding

compute_velocity_colors(vel: np.ndarray | None, cmap_name: str = 'viridis', fallback_length: int | None = None, cmap: Colormap | None = None) -> np.ndarray

Compute RGB colors based on velocity magnitude.

Parameters:

Name Type Description Default
vel ndarray | None

Velocity array of shape (N, 3), or None if velocity is not available

required
cmap_name str

Matplotlib colormap name

'viridis'
fallback_length int | None

When vel is None, number of points for default color array. Required when vel is None.

None
cmap Colormap | None

Preloaded colormap to use instead of looking up cmap_name. Pass one when recoloring every frame of a realtime loop, where the lookup would otherwise repeat per call.

None

Returns:

Type Description
ndarray

Array of RGB colors with shape (N, 3), values in [0, 255]

Source code in openscvx/plotting/viser/server.py
def compute_velocity_colors(
    vel: np.ndarray | None,
    cmap_name: str = "viridis",
    fallback_length: int | None = None,
    cmap: Colormap | None = None,
) -> np.ndarray:
    """Compute RGB colors based on velocity magnitude.

    Args:
        vel: Velocity array of shape (N, 3), or None if velocity is not available
        cmap_name: Matplotlib colormap name
        fallback_length: When vel is None, number of points for default color array.
            Required when vel is None.
        cmap: Preloaded colormap to use instead of looking up ``cmap_name``. Pass
            one when recoloring every frame of a realtime loop, where the lookup
            would otherwise repeat per call.

    Returns:
        Array of RGB colors with shape (N, 3), values in [0, 255]
    """
    if cmap is None:
        cmap = plt.get_cmap(cmap_name)

    if vel is None:
        if fallback_length is None:
            raise ValueError("fallback_length is required when vel is None")
        # Single default color (viridis mid) for all points
        default_rgb = np.array([int(c * 255) for c in cmap(0.5)[:3]], dtype=np.uint8)
        return np.broadcast_to(default_rgb, (fallback_length, 3)).copy()
    vel_norms = np.linalg.norm(vel, axis=1)
    vel_range = vel_norms.max() - vel_norms.min()
    if vel_range < 1e-8:
        vel_normalized = np.zeros_like(vel_norms)
    else:
        vel_normalized = (vel_norms - vel_norms.min()) / vel_range

    return (np.asarray(cmap(vel_normalized))[:, :3] * 255).astype(int)

create_server(pos: np.ndarray | None, dark_mode: bool = True, show_grid: bool = True, port: int | None = None, *, show_origin: bool = True) -> viser.ViserServer

Create a viser server with basic scene setup.

Prefer this over a bare viser.ViserServer(): it is the only thing that attaches the OpenSCvx titlebar (logo and documentation links), so a scene built without it silently ships unbranded.

Parameters:

Name Type Description Default
pos ndarray | None

Position array for computing grid size, or None to use default grid size

required
dark_mode bool

Whether to use dark theme

True
show_grid bool

Whether to show the grid (default True)

True
port int | None

Preferred HTTP port. Defaults to 8080. If that port is already bound on localhost, the next free port is used.

None
show_origin bool

Whether to draw the origin triad. The frame is half a metre across, so switch it off for scenes whose whole extent is on that order (a scale-model track, a tabletop workspace) where it would dominate rather than orient.

True

Returns:

Type Description
ViserServer

ViserServer instance with the titlebar, and optionally a grid and origin frame

Source code in openscvx/plotting/viser/server.py
def create_server(
    pos: np.ndarray | None,
    dark_mode: bool = True,
    show_grid: bool = True,
    port: int | None = None,
    *,
    show_origin: bool = True,
) -> viser.ViserServer:
    """Create a viser server with basic scene setup.

    Prefer this over a bare ``viser.ViserServer()``: it is the only thing that
    attaches the OpenSCvx titlebar (logo and documentation links), so a scene
    built without it silently ships unbranded.

    Args:
        pos: Position array for computing grid size, or None to use default grid size
        dark_mode: Whether to use dark theme
        show_grid: Whether to show the grid (default True)
        port: Preferred HTTP port. Defaults to 8080. If that port is already
            bound on localhost, the next free port is used.
        show_origin: Whether to draw the origin triad. The frame is half a metre
            across, so switch it off for scenes whose whole extent is on that
            order (a scale-model track, a tabletop workspace) where it would
            dominate rather than orient.

    Returns:
        ViserServer instance with the titlebar, and optionally a grid and origin frame
    """
    server = viser.ViserServer(port=_resolve_viser_port(port))

    # Configure theme with OpenSCvx branding
    # TitlebarButton and TitlebarConfig are TypedDict classes (create as plain dicts)
    buttons = (
        TitlebarButton(
            text="API Reference",
            icon="Description",
            href="https://openscvx.github.io/OpenSCvx/latest/Reference/",
        ),
        TitlebarButton(
            text="Docs",
            icon="Description",
            href="https://openscvx.github.io/OpenSCvx/",
        ),
        TitlebarButton(
            text="GitHub",
            icon="GitHub",
            href="https://github.com/OpenSCvx/OpenSCvx",
        ),
    )

    # Add OpenSCvx logo to titlebar (loaded from GitHub)
    logo_url = (
        "https://raw.githubusercontent.com/OpenSCvx/OpenSCvx/main/figures/openscvx_logo_square.png"
    )
    image = TitlebarImage(
        image_url_light=logo_url,
        image_url_dark=logo_url,  # Use same logo for both themes
        image_alt="OpenSCvx",
        href="https://github.com/OpenSCvx/OpenSCvx",
    )

    titlebar_config = TitlebarConfig(buttons=buttons, image=image)

    server.gui.configure_theme(
        titlebar_content=titlebar_config,
        dark_mode=dark_mode,
    )

    if show_grid:
        grid_size = compute_grid_size(pos)
        server.scene.add_grid(
            "/grid",
            width=grid_size,
            height=grid_size,
            position=np.array([0.0, 0.0, 0.0]),
        )
    if show_origin:
        server.scene.add_frame(
            "/origin",
            wxyz=(1.0, 0.0, 0.0, 0.0),
            position=(0.0, 0.0, 0.0),
        )

    return server