Skip to content

Commit

Permalink
Cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
brentyi committed Sep 10, 2024
1 parent 297dbd4 commit 631425c
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 86 deletions.
4 changes: 2 additions & 2 deletions docs/source/scene_handles.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ connected clients. When a scene node is added to a client (for example, via
The most common attributes to read and write here are
:attr:`viser.SceneNodeHandle.wxyz` and :attr:`viser.SceneNodeHandle.position`.
Each node type also has type-specific attributes that we can read and write.
Note that many of these are lower-level than their equivalent arguments in
factory methods like :func:`viser.ViserServer.add_frame()` or
Many of these are lower-level than their equivalent arguments in factory
methods like :func:`viser.ViserServer.add_frame()` or
:func:`viser.ViserServer.add_image()`.

<!-- prettier-ignore-start -->
Expand Down
52 changes: 28 additions & 24 deletions examples/11_colmap_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import random
import time
from pathlib import Path
from typing import List

import imageio.v3 as iio
import numpy as onp
Expand Down Expand Up @@ -67,26 +68,30 @@ def _(event: viser.GuiEvent) -> None:
step=1,
initial_value=min(len(images), 100),
)
gui_point_size = server.gui.add_number("Point size", initial_value=0.05)
gui_point_size = server.gui.add_slider(
"Point size", min=0.01, max=0.1, step=0.001, initial_value=0.05
)

points = onp.array([points3d[p_id].xyz for p_id in points3d])
colors = onp.array([points3d[p_id].rgb for p_id in points3d])

point_mask = onp.random.choice(points.shape[0], gui_points.value, replace=False)
point_cloud = server.scene.add_point_cloud(
name="/colmap/pcd",
points=points[point_mask],
colors=colors[point_mask],
point_size=gui_point_size.value,
)
frames: List[viser.FrameHandle] = []

def visualize_colmap() -> None:
def visualize_frames() -> None:
"""Send all COLMAP elements to viser for visualization. This could be optimized
a ton!"""
# Set the point cloud.
points = onp.array([points3d[p_id].xyz for p_id in points3d])
colors = onp.array([points3d[p_id].rgb for p_id in points3d])
points_selection = onp.random.choice(
points.shape[0], gui_points.value, replace=False
)
points = points[points_selection]
colors = colors[points_selection]

server.scene.add_point_cloud(
name="/colmap/pcd",
points=points,
colors=colors,
point_size=gui_point_size.value,
)

# Remove existing image frames.
for frame in frames:
frame.remove()
frames.clear()

# Interpret the images and cameras.
img_ids = [im.id for im in images.values()]
Expand Down Expand Up @@ -121,6 +126,7 @@ def _(_) -> None:
axes_length=0.1,
axes_radius=0.005,
)
frames.append(frame)

# For pinhole cameras, cam.params will be (fx, fy, cx, cy).
if cam.model != "PINHOLE":
Expand All @@ -143,8 +149,9 @@ def _(_) -> None:

@gui_points.on_update
def _(_) -> None:
nonlocal need_update
need_update = True
point_mask = onp.random.choice(points.shape[0], gui_points.value, replace=False)
point_cloud.points = points[point_mask]
point_cloud.colors = colors[point_mask]

@gui_frames.on_update
def _(_) -> None:
Expand All @@ -153,15 +160,12 @@ def _(_) -> None:

@gui_point_size.on_update
def _(_) -> None:
nonlocal need_update
need_update = True
point_cloud.point_size = gui_point_size.value

while True:
if need_update:
need_update = False

server.scene.reset()
visualize_colmap()
visualize_frames()

time.sleep(1e-3)

Expand Down
24 changes: 12 additions & 12 deletions src/viser/_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ class CameraFrustumProps:
"""Aspect ratio of the camera (width over height). For handles, synchronized automatically when assigned."""
scale: float
"""Scale factor for the size of the frustum. For handles, synchronized automatically when assigned."""
color: int
"""Color of the frustum as an RGB integer. For handles, synchronized automatically when assigned."""
color: Tuple[int, int, int]
"""Color of the frustum as RGB integers. For handles, synchronized automatically when assigned."""
image_media_type: Optional[Literal["image/jpeg", "image/png"]]
"""Format of the provided image ('image/jpeg' or 'image/png'). For handles, synchronized automatically when assigned."""
image_binary: Optional[bytes]
Expand Down Expand Up @@ -261,14 +261,14 @@ class GridProps:
"""Number of segments along the height. For handles, synchronized automatically when assigned."""
plane: Literal["xz", "xy", "yx", "yz", "zx", "zy"]
"""The plane in which the grid is oriented. For handles, synchronized automatically when assigned."""
cell_color: int
"""Color of the grid cells as an RGB integer. For handles, synchronized automatically when assigned."""
cell_color: Tuple[int, int, int]
"""Color of the grid cells as RGB integers. For handles, synchronized automatically when assigned."""
cell_thickness: float
"""Thickness of the grid lines. For handles, synchronized automatically when assigned."""
cell_size: float
"""Size of each cell in the grid. For handles, synchronized automatically when assigned."""
section_color: int
"""Color of the grid sections as an RGB integer. For handles, synchronized automatically when assigned."""
section_color: Tuple[int, int, int]
"""Color of the grid sections as RGB integers. For handles, synchronized automatically when assigned."""
section_thickness: float
"""Thickness of the section lines. For handles, synchronized automatically when assigned."""
section_size: float
Expand Down Expand Up @@ -355,8 +355,8 @@ class MeshProps:
"""A numpy array of vertex positions. Should have shape (V, 3). For handles, synchronized automatically when assigned."""
faces: onpt.NDArray[onp.uint32]
"""A numpy array of faces, where each face is represented by indices of vertices. Should have shape (F, 3). For handles, synchronized automatically when assigned."""
color: Optional[int]
"""Color of the mesh as an RGB integer. For handles, synchronized automatically when assigned."""
color: Optional[Tuple[int, int, int]]
"""Color of the mesh as RGB integers. For handles, synchronized automatically when assigned."""
vertex_colors: Optional[onpt.NDArray[onp.uint8]]
"""Optional array of vertex colors. For handles, synchronized automatically when assigned."""
wireframe: bool
Expand Down Expand Up @@ -871,8 +871,8 @@ class CatmullRomSplineProps:
"""Boolean indicating if the spline is closed (forms a loop). For handles, synchronized automatically when assigned."""
line_width: float
"""Width of the spline line. For handles, synchronized automatically when assigned."""
color: int
"""Color of the spline as an RGB integer. For handles, synchronized automatically when assigned."""
color: Tuple[int, int, int]
"""Color of the spline as RGB integers. For handles, synchronized automatically when assigned."""
segments: Optional[int]
"""Number of segments to divide the spline into. For handles, synchronized automatically when assigned."""

Expand All @@ -893,8 +893,8 @@ class CubicBezierSplineProps:
"""A tuple of control points for Bezier curve shaping. For handles, synchronized automatically when assigned."""
line_width: float
"""Width of the spline line. For handles, synchronized automatically when assigned."""
color: int
"""Color of the spline as an RGB integer. For handles, synchronized automatically when assigned."""
color: Tuple[int, int, int]
"""Color of the spline as RGB integers. For handles, synchronized automatically when assigned."""
segments: Optional[int]
"""Number of segments to divide the spline into. For handles, synchronized automatically when assigned."""

Expand Down
27 changes: 8 additions & 19 deletions src/viser/_scene_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import imageio.v3 as iio
import numpy as onp
import numpy.typing as onpt
from typing_extensions import Literal, ParamSpec, TypeAlias, assert_never

from . import _messages
Expand All @@ -32,6 +31,7 @@
ScenePointerEvent,
TransformControlsHandle,
_TransformControlsState,
colors_to_uint8,
)

if TYPE_CHECKING:
Expand All @@ -44,31 +44,20 @@
P = ParamSpec("P")


def _colors_to_uint8(colors: onp.ndarray) -> onpt.NDArray[onp.uint8]:
"""Convert intensity values to uint8. We assume the range [0,1] for floats, and
[0,255] for integers. Accepts any shape."""
if colors.dtype != onp.uint8:
if onp.issubdtype(colors.dtype, onp.floating):
colors = onp.clip(colors * 255.0, 0, 255).astype(onp.uint8)
if onp.issubdtype(colors.dtype, onp.integer):
colors = onp.clip(colors, 0, 255).astype(onp.uint8)
return colors


RgbTupleOrArray: TypeAlias = Union[
Tuple[int, int, int], Tuple[float, float, float], onp.ndarray
]


def _encode_rgb(rgb: RgbTupleOrArray) -> int:
def _encode_rgb(rgb: RgbTupleOrArray) -> tuple[int, int, int]:
if isinstance(rgb, onp.ndarray):
assert rgb.shape == (3,)
rgb_fixed = tuple(
value if onp.issubdtype(type(value), onp.integer) else int(value * 255)
int(value) if onp.issubdtype(type(value), onp.integer) else int(value * 255)
for value in rgb
)
assert len(rgb_fixed) == 3
return int(rgb_fixed[0] * (256**2) + rgb_fixed[1] * 256 + rgb_fixed[2])
return rgb_fixed # type: ignore


def _encode_image_binary(
Expand All @@ -77,7 +66,7 @@ def _encode_image_binary(
jpeg_quality: int | None = None,
) -> tuple[Literal["image/png", "image/jpeg"], bytes]:
media_type: Literal["image/png", "image/jpeg"]
image = _colors_to_uint8(image)
image = colors_to_uint8(image)
with io.BytesIO() as data_buffer:
if format == "png":
media_type = "image/png"
Expand Down Expand Up @@ -673,7 +662,7 @@ def add_point_cloud(
Returns:
Handle for manipulating scene node.
"""
colors_cast = _colors_to_uint8(onp.asarray(colors))
colors_cast = colors_to_uint8(onp.asarray(colors))
assert (
len(points.shape) == 2 and points.shape[-1] == 3
), "Shape of points should be (N, 3)."
Expand Down Expand Up @@ -975,8 +964,8 @@ def _add_gaussian_splats(
# - xyz (96 bits): upper-triangular Cholesky factor of covariance.
cov_cholesky_triu.astype(onp.float16).copy().view(onp.uint8),
# - w (32 bits): rgba.
_colors_to_uint8(rgbs),
_colors_to_uint8(opacities),
colors_to_uint8(rgbs),
colors_to_uint8(opacities),
],
axis=-1,
).view(onp.uint32)
Expand Down
29 changes: 25 additions & 4 deletions src/viser/_scene_handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
Any,
Callable,
ClassVar,
Dict,
Generic,
Literal,
Protocol,
Expand All @@ -15,6 +16,8 @@
)

import numpy as onp
import numpy.typing as onpt
from typing_extensions import get_type_hints

from . import _messages
from .infra._infra import WebsockClientConnection, WebsockServer
Expand All @@ -26,21 +29,39 @@
from .infra import ClientId


def colors_to_uint8(colors: onp.ndarray) -> onpt.NDArray[onp.uint8]:
"""Convert intensity values to uint8. We assume the range [0,1] for floats, and
[0,255] for integers. Accepts any shape."""
if colors.dtype != onp.uint8:
if onp.issubdtype(colors.dtype, onp.floating):
colors = onp.clip(colors * 255.0, 0, 255).astype(onp.uint8)
if onp.issubdtype(colors.dtype, onp.integer):
colors = onp.clip(colors, 0, 255).astype(onp.uint8)
return colors


class _OverridablePropApi:
"""Mixin that allows reading/assigning properties defined in each scene node message."""

_PropClass: ClassVar[type]
_PropHints: ClassVar[Dict[str, type]]

def __init__(self) -> None:
assert False

def __init_subclass__(cls, PropClass: type):
cls._PropClass = PropClass
cls._PropHints = get_type_hints(PropClass)

def __setattr__(self, name: str, value: Any) -> None:
handle = cast(SceneNodeHandle, self)
# Get the value of the T TypeVar.
if name in self._PropClass.__annotations__:
if name in self._PropHints:
# Help the user with some casting...
hint = self._PropHints[name]
if hint == onpt.NDArray[onp.float32]:
value = value.astype(onp.float32)
elif hint == onpt.NDArray[onp.uint8] and "color" in name:
value = colors_to_uint8(value)

setattr(handle._impl.props, name, value)
handle._impl.api._websock_interface.queue_message(
_messages.SceneNodeUpdateMessage(handle.name, {name: value})
Expand All @@ -49,7 +70,7 @@ def __setattr__(self, name: str, value: Any) -> None:
return object.__setattr__(self, name, value)

def __getattr__(self, name: str) -> Any:
if name in self._PropClass.__annotations__:
if name in self._PropClass:
return getattr(self._impl.props, name)
else:
raise AttributeError(
Expand Down
14 changes: 9 additions & 5 deletions src/viser/client/src/SceneTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ import GeneratedGuiContainer from "./ControlPanel/Generated";
/** Type corresponding to a zustand-style useSceneTree hook. */
export type UseSceneTree = ReturnType<typeof useSceneTreeState>;

function rgbToInt(rgb: [number, number, number]): number {
return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
}

function SceneNodeThreeChildren(props: {
name: string;
parent: THREE.Object3D;
Expand Down Expand Up @@ -197,10 +201,10 @@ function useObjectFactory(message: SceneNodeMessage | undefined): {
message.props.height_segments,
]}
side={THREE.DoubleSide}
cellColor={message.props.cell_color}
cellColor={rgbToInt(message.props.cell_color)}
cellThickness={message.props.cell_thickness}
cellSize={message.props.cell_size}
sectionColor={message.props.section_color}
sectionColor={rgbToInt(message.props.section_color)}
sectionThickness={message.props.section_thickness}
sectionSize={message.props.section_size}
rotation={
Expand Down Expand Up @@ -274,7 +278,7 @@ function useObjectFactory(message: SceneNodeMessage | undefined): {
fov={message.props.fov}
aspect={message.props.aspect}
scale={message.props.scale}
color={message.props.color}
color={rgbToInt(message.props.color)}
imageBinary={message.props.image_binary}
imageMediaType={message.props.image_media_type}
/>
Expand Down Expand Up @@ -425,7 +429,7 @@ function useObjectFactory(message: SceneNodeMessage | undefined): {
curveType={message.props.curve_type}
tension={message.props.tension}
lineWidth={message.props.line_width}
color={message.props.color}
color={rgbToInt(message.props.color)}
// Sketchy cast needed due to https://github.com/pmndrs/drei/issues/1476.
segments={(message.props.segments ?? undefined) as undefined}
/>
Expand All @@ -446,7 +450,7 @@ function useObjectFactory(message: SceneNodeMessage | undefined): {
midA={message.props.control_points[2 * i]}
midB={message.props.control_points[2 * i + 1]}
lineWidth={message.props.line_width}
color={message.props.color}
color={rgbToInt(message.props.color)}
// Sketchy cast needed due to https://github.com/pmndrs/drei/issues/1476.
segments={(message.props.segments ?? undefined) as undefined}
></CubicBezierLine>
Expand Down
Loading

0 comments on commit 631425c

Please sign in to comment.