diff --git a/latest/.doctrees/environment.pickle b/latest/.doctrees/environment.pickle index 467d4cd2..cb874ca3 100644 Binary files a/latest/.doctrees/environment.pickle and b/latest/.doctrees/environment.pickle differ diff --git a/latest/.doctrees/examples/07_record3d_visualizer.doctree b/latest/.doctrees/examples/07_record3d_visualizer.doctree index f34e207d..c2e735ff 100644 Binary files a/latest/.doctrees/examples/07_record3d_visualizer.doctree and b/latest/.doctrees/examples/07_record3d_visualizer.doctree differ diff --git a/latest/.doctrees/examples/08_smpl_visualizer.doctree b/latest/.doctrees/examples/08_smpl_visualizer.doctree index 2fed7add..a9f9043e 100644 Binary files a/latest/.doctrees/examples/08_smpl_visualizer.doctree and b/latest/.doctrees/examples/08_smpl_visualizer.doctree differ diff --git a/latest/.doctrees/examples/25_smpl_visualizer_skinned.doctree b/latest/.doctrees/examples/25_smpl_visualizer_skinned.doctree index a45b482b..08d95909 100644 Binary files a/latest/.doctrees/examples/25_smpl_visualizer_skinned.doctree and b/latest/.doctrees/examples/25_smpl_visualizer_skinned.doctree differ diff --git a/latest/.doctrees/scene_api.doctree b/latest/.doctrees/scene_api.doctree index 28542176..b44b5d14 100644 Binary files a/latest/.doctrees/scene_api.doctree and b/latest/.doctrees/scene_api.doctree differ diff --git a/latest/_modules/viser/_scene_api/index.html b/latest/_modules/viser/_scene_api/index.html index 0722ed28..e0fccbc1 100644 --- a/latest/_modules/viser/_scene_api/index.html +++ b/latest/_modules/viser/_scene_api/index.html @@ -1639,7 +1639,13 @@
)
) -> GaussianSplatHandle:
"""Add a model to render using Gaussian Splatting.
- **Work-in-progress.** This feature is experimental and still under
+ **Experimental.** This feature is experimental and still under
development. It may be changed or removed.
Arguments:
@@ -1708,7 +1714,8 @@ Source code for viser._scene_api
node_handle = GaussianSplatHandle._make(
self, message, name, wxyz, position, visible
)
- return node_handle
+ return node_handle
+
[docs]
diff --git a/latest/_sources/examples/07_record3d_visualizer.rst.txt b/latest/_sources/examples/07_record3d_visualizer.rst.txt
index 06bf6932..89b0df03 100644
--- a/latest/_sources/examples/07_record3d_visualizer.rst.txt
+++ b/latest/_sources/examples/07_record3d_visualizer.rst.txt
@@ -18,11 +18,10 @@ Parse and stream record3d captures. To get the demo data, see ``./assets/downloa
import numpy as np
import tyro
- from tqdm.auto import tqdm
-
import viser
import viser.extras
import viser.transforms as tf
+ from tqdm.auto import tqdm
def main(
@@ -41,6 +40,13 @@ Parse and stream record3d captures. To get the demo data, see ``./assets/downloa
# Add playback UI.
with server.gui.add_folder("Playback"):
+ gui_point_size = server.gui.add_slider(
+ "Point size",
+ min=0.001,
+ max=0.02,
+ step=1e-3,
+ initial_value=0.01,
+ )
gui_timestep = server.gui.add_slider(
"Timestep",
min=0,
@@ -88,6 +94,10 @@ Parse and stream record3d captures. To get the demo data, see ``./assets/downloa
nonlocal prev_timestep
current_timestep = gui_timestep.value
with server.atomic():
+ # Update point size.
+ point_nodes[current_timestep].point_size = gui_point_size.value
+
+ # Toggle visibility.
frame_nodes[current_timestep].visible = True
frame_nodes[prev_timestep].visible = False
prev_timestep = current_timestep
@@ -101,6 +111,7 @@ Parse and stream record3d captures. To get the demo data, see ``./assets/downloa
show_axes=False,
)
frame_nodes: list[viser.FrameHandle] = []
+ point_nodes: list[viser.PointCloudHandle] = []
for i in tqdm(range(num_frames)):
frame = loader.get_frame(i)
position, color = frame.get_point_cloud(downsample_factor)
@@ -109,12 +120,14 @@ Parse and stream record3d captures. To get the demo data, see ``./assets/downloa
frame_nodes.append(server.scene.add_frame(f"/frames/t{i}", show_axes=False))
# Place the point cloud in the frame.
- server.scene.add_point_cloud(
- name=f"/frames/t{i}/point_cloud",
- points=position,
- colors=color,
- point_size=0.01,
- point_shape="rounded",
+ point_nodes.append(
+ server.scene.add_point_cloud(
+ name=f"/frames/t{i}/point_cloud",
+ points=position,
+ colors=color,
+ point_size=gui_point_size.value,
+ point_shape="rounded",
+ )
)
# Place the frustum.
diff --git a/latest/_sources/examples/08_smpl_visualizer.rst.txt b/latest/_sources/examples/08_smpl_visualizer.rst.txt
index c390d42d..6d808008 100644
--- a/latest/_sources/examples/08_smpl_visualizer.rst.txt
+++ b/latest/_sources/examples/08_smpl_visualizer.rst.txt
@@ -44,21 +44,21 @@ See here for download instructions:
assert model_path.suffix.lower() == ".npz", "Model should be an .npz file!"
body_dict = dict(**np.load(model_path, allow_pickle=True))
- self._J_regressor = body_dict["J_regressor"]
- self._weights = body_dict["weights"]
- self._v_template = body_dict["v_template"]
- self._posedirs = body_dict["posedirs"]
- self._shapedirs = body_dict["shapedirs"]
- self._faces = body_dict["f"]
-
- self.num_joints: int = self._weights.shape[-1]
- self.num_betas: int = self._shapedirs.shape[-1]
+ self.J_regressor = body_dict["J_regressor"]
+ self.weights = body_dict["weights"]
+ self.v_template = body_dict["v_template"]
+ self.posedirs = body_dict["posedirs"]
+ self.shapedirs = body_dict["shapedirs"]
+ self.faces = body_dict["f"]
+
+ self.num_joints: int = self.weights.shape[-1]
+ self.num_betas: int = self.shapedirs.shape[-1]
self.parent_idx: np.ndarray = body_dict["kintree_table"][0]
def get_outputs(self, betas: np.ndarray, joint_rotmats: np.ndarray) -> SmplOutputs:
# Get shaped vertices + joint positions, when all local poses are identity.
- v_tpose = self._v_template + np.einsum("vxb,b->vx", self._shapedirs, betas)
- j_tpose = np.einsum("jv,vx->jx", self._J_regressor, v_tpose)
+ v_tpose = self.v_template + np.einsum("vxb,b->vx", self.shapedirs, betas)
+ j_tpose = np.einsum("jv,vx->jx", self.J_regressor, v_tpose)
# Local SE(3) transforms.
T_parent_joint = np.zeros((self.num_joints, 4, 4)) + np.eye(4)
@@ -73,13 +73,13 @@ See here for download instructions:
# Linear blend skinning.
pose_delta = (joint_rotmats[1:, ...] - np.eye(3)).flatten()
- v_blend = v_tpose + np.einsum("byn,n->by", self._posedirs, pose_delta)
+ v_blend = v_tpose + np.einsum("byn,n->by", self.posedirs, pose_delta)
v_delta = np.ones((v_blend.shape[0], self.num_joints, 4))
v_delta[:, :, :3] = v_blend[:, None, :] - j_tpose[None, :, :]
v_posed = np.einsum(
- "jxy,vj,vjy->vx", T_world_joint[:, :3, :], self._weights, v_delta
+ "jxy,vj,vjy->vx", T_world_joint[:, :3, :], self.weights, v_delta
)
- return SmplOutputs(v_posed, self._faces, T_world_joint, T_parent_joint)
+ return SmplOutputs(v_posed, self.faces, T_world_joint, T_parent_joint)
def main(model_path: Path) -> None:
@@ -96,6 +96,13 @@ See here for download instructions:
num_joints=model.num_joints,
parent_idx=model.parent_idx,
)
+ body_handle = server.scene.add_mesh_simple(
+ "/human",
+ model.v_template,
+ model.faces,
+ wireframe=gui_elements.gui_wireframe.value,
+ color=gui_elements.gui_rgb.value,
+ )
while True:
# Do nothing if no change.
time.sleep(0.02)
@@ -104,7 +111,7 @@ See here for download instructions:
gui_elements.changed = False
- # Compute SMPL outputs.
+ # If anything has changed, re-compute SMPL outputs.
smpl_outputs = model.get_outputs(
betas=np.array([x.value for x in gui_elements.gui_betas]),
joint_rotmats=tf.SO3.exp(
@@ -112,13 +119,12 @@ See here for download instructions:
np.array([x.value for x in gui_elements.gui_joints])
).as_matrix(),
)
- server.scene.add_mesh_simple(
- "/human",
- smpl_outputs.vertices,
- smpl_outputs.faces,
- wireframe=gui_elements.gui_wireframe.value,
- color=gui_elements.gui_rgb.value,
- )
+
+ # Update the mesh properties based on the SMPL model output + GUI
+ # elements.
+ body_handle.vertices = smpl_outputs.vertices
+ body_handle.wireframe = gui_elements.gui_wireframe.value
+ body_handle.color = gui_elements.gui_rgb.value
# Match transform control gizmos to joint positions.
for i, control in enumerate(gui_elements.transform_controls):
@@ -156,7 +162,7 @@ See here for download instructions:
with tab_group.add_tab("View", viser.Icon.VIEWFINDER):
gui_rgb = server.gui.add_rgb("Color", initial_value=(90, 200, 255))
gui_wireframe = server.gui.add_checkbox("Wireframe", initial_value=False)
- gui_show_controls = server.gui.add_checkbox("Handles", initial_value=False)
+ gui_show_controls = server.gui.add_checkbox("Handles", initial_value=True)
gui_rgb.on_update(set_changed)
gui_wireframe.on_update(set_changed)
diff --git a/latest/_sources/examples/25_smpl_visualizer_skinned.rst.txt b/latest/_sources/examples/25_smpl_visualizer_skinned.rst.txt
index caa8a522..1917d2ed 100644
--- a/latest/_sources/examples/25_smpl_visualizer_skinned.rst.txt
+++ b/latest/_sources/examples/25_smpl_visualizer_skinned.rst.txt
@@ -45,21 +45,27 @@ See here for download instructions:
assert model_path.suffix.lower() == ".npz", "Model should be an .npz file!"
body_dict = dict(**np.load(model_path, allow_pickle=True))
- self._J_regressor = body_dict["J_regressor"]
- self._weights = body_dict["weights"]
- self._v_template = body_dict["v_template"]
- self._posedirs = body_dict["posedirs"]
- self._shapedirs = body_dict["shapedirs"]
- self._faces = body_dict["f"]
-
- self.num_joints: int = self._weights.shape[-1]
- self.num_betas: int = self._shapedirs.shape[-1]
+ self.J_regressor = body_dict["J_regressor"]
+ self.weights = body_dict["weights"]
+ self.v_template = body_dict["v_template"]
+ self.posedirs = body_dict["posedirs"]
+ self.shapedirs = body_dict["shapedirs"]
+ self.faces = body_dict["f"]
+
+ self.num_joints: int = self.weights.shape[-1]
+ self.num_betas: int = self.shapedirs.shape[-1]
self.parent_idx: np.ndarray = body_dict["kintree_table"][0]
+ def get_tpose(self, betas: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
+ # Get shaped vertices + joint positions, when all local poses are identity.
+ v_tpose = self.v_template + np.einsum("vxb,b->vx", self.shapedirs, betas)
+ j_tpose = np.einsum("jv,vx->jx", self.J_regressor, v_tpose)
+ return v_tpose, j_tpose
+
def get_outputs(self, betas: np.ndarray, joint_rotmats: np.ndarray) -> SmplOutputs:
# Get shaped vertices + joint positions, when all local poses are identity.
- v_tpose = self._v_template + np.einsum("vxb,b->vx", self._shapedirs, betas)
- j_tpose = np.einsum("jv,vx->jx", self._J_regressor, v_tpose)
+ v_tpose = self.v_template + np.einsum("vxb,b->vx", self.shapedirs, betas)
+ j_tpose = np.einsum("jv,vx->jx", self.J_regressor, v_tpose)
# Local SE(3) transforms.
T_parent_joint = np.zeros((self.num_joints, 4, 4)) + np.eye(4)
@@ -74,13 +80,13 @@ See here for download instructions:
# Linear blend skinning.
pose_delta = (joint_rotmats[1:, ...] - np.eye(3)).flatten()
- v_blend = v_tpose + np.einsum("byn,n->by", self._posedirs, pose_delta)
+ v_blend = v_tpose + np.einsum("byn,n->by", self.posedirs, pose_delta)
v_delta = np.ones((v_blend.shape[0], self.num_joints, 4))
v_delta[:, :, :3] = v_blend[:, None, :] - j_tpose[None, :, :]
v_posed = np.einsum(
- "jxy,vj,vjy->vx", T_world_joint[:, :3, :], self._weights, v_delta
+ "jxy,vj,vjy->vx", T_world_joint[:, :3, :], self.weights, v_delta
)
- return SmplOutputs(v_posed, self._faces, T_world_joint, T_parent_joint)
+ return SmplOutputs(v_posed, self.faces, T_world_joint, T_parent_joint)
def main(model_path: Path) -> None:
@@ -97,23 +103,14 @@ See here for download instructions:
num_joints=model.num_joints,
parent_idx=model.parent_idx,
)
- smpl_outputs = model.get_outputs(
- betas=np.array([x.value for x in gui_elements.gui_betas]),
- joint_rotmats=np.zeros((model.num_joints, 3, 3)) + np.eye(3),
- )
-
- bone_wxyzs = np.array(
- [tf.SO3.from_matrix(R).wxyz for R in smpl_outputs.T_world_joint[:, :3, :3]]
- )
- bone_positions = smpl_outputs.T_world_joint[:, :3, 3]
-
- skinned_handle = server.scene.add_mesh_skinned(
+ v_tpose, j_tpose = model.get_tpose(np.zeros((model.num_betas,)))
+ mesh_handle = server.scene.add_mesh_skinned(
"/human",
- smpl_outputs.vertices,
- smpl_outputs.faces,
- bone_wxyzs=bone_wxyzs,
- bone_positions=bone_positions,
- skin_weights=model._weights,
+ v_tpose,
+ model.faces,
+ bone_wxyzs=tf.SO3.identity(batch_axes=(model.num_joints,)).wxyz,
+ bone_positions=j_tpose,
+ skin_weights=model.weights,
wireframe=gui_elements.gui_wireframe.value,
color=gui_elements.gui_rgb.value,
)
@@ -124,10 +121,19 @@ See here for download instructions:
if not gui_elements.changed:
continue
+ # Shapes changed: update vertices / joint positions.
+ if gui_elements.betas_changed:
+ v_tpose, j_tpose = model.get_tpose(
+ np.array([gui_beta.value for gui_beta in gui_elements.gui_betas])
+ )
+ mesh_handle.vertices = v_tpose
+ mesh_handle.bone_positions = j_tpose
+
gui_elements.changed = False
+ gui_elements.betas_changed = False
# Render as wireframe?
- skinned_handle.wireframe = gui_elements.gui_wireframe.value
+ mesh_handle.wireframe = gui_elements.gui_wireframe.value
# Compute SMPL outputs.
smpl_outputs = model.get_outputs(
@@ -144,10 +150,10 @@ See here for download instructions:
# Match transform control gizmos to joint positions.
for i, control in enumerate(gui_elements.transform_controls):
control.position = smpl_outputs.T_parent_joint[i, :3, 3]
- skinned_handle.bones[i].wxyz = tf.SO3.from_matrix(
+ mesh_handle.bones[i].wxyz = tf.SO3.from_matrix(
smpl_outputs.T_world_joint[i, :3, :3]
).wxyz
- skinned_handle.bones[i].position = smpl_outputs.T_world_joint[i, :3, 3]
+ mesh_handle.bones[i].position = smpl_outputs.T_world_joint[i, :3, 3]
@dataclass
@@ -161,7 +167,10 @@ See here for download instructions:
transform_controls: List[viser.TransformControlsHandle]
changed: bool
- """This flag will be flipped to True whenever the mesh needs to be re-generated."""
+ """This flag will be flipped to True whenever any input is changed."""
+
+ betas_changed: bool
+ """This flag will be flipped to True whenever the shape changes."""
def make_gui_elements(
@@ -175,7 +184,11 @@ See here for download instructions:
tab_group = server.gui.add_tab_group()
def set_changed(_) -> None:
- out.changed = True # out is define later!
+ out.changed = True # out is defined later!
+
+ def set_betas_changed(_) -> None:
+ out.betas_changed = True
+ out.changed = True
# GUI elements: mesh settings + visibility.
with tab_group.add_tab("View", viser.Icon.VIEWFINDER):
@@ -225,7 +238,7 @@ See here for download instructions:
f"beta{i}", min=-5.0, max=5.0, step=0.01, initial_value=0.0
)
gui_betas.append(beta)
- beta.on_update(set_changed)
+ beta.on_update(set_betas_changed)
# GUI elements: joint angles.
with tab_group.add_tab("Joints", viser.Icon.ANGLE):
@@ -300,6 +313,7 @@ See here for download instructions:
gui_joints,
transform_controls=transform_controls,
changed=True,
+ betas_changed=False,
)
return out
diff --git a/latest/examples/07_record3d_visualizer/index.html b/latest/examples/07_record3d_visualizer/index.html
index 790e8cc4..d0594fc6 100644
--- a/latest/examples/07_record3d_visualizer/index.html
+++ b/latest/examples/07_record3d_visualizer/index.html
@@ -396,140 +396,153 @@ Record3D visualizer 3
4import numpy as np
5import tyro
- 6from tqdm.auto import tqdm
- 7
- 8import viser
- 9import viser.extras
- 10import viser.transforms as tf
+ 6import viser
+ 7import viser.extras
+ 8import viser.transforms as tf
+ 9from tqdm.auto import tqdm
+ 10
11
- 12
- 13def main(
- 14 data_path: Path = Path(__file__).parent / "assets/record3d_dance",
- 15 downsample_factor: int = 4,
- 16 max_frames: int = 100,
- 17 share: bool = False,
- 18) -> None:
- 19 server = viser.ViserServer()
- 20 if share:
- 21 server.request_share_url()
- 22
- 23 print("Loading frames!")
- 24 loader = viser.extras.Record3dLoader(data_path)
- 25 num_frames = min(max_frames, loader.num_frames())
- 26
- 27 # Add playback UI.
- 28 with server.gui.add_folder("Playback"):
- 29 gui_timestep = server.gui.add_slider(
- 30 "Timestep",
- 31 min=0,
- 32 max=num_frames - 1,
- 33 step=1,
- 34 initial_value=0,
- 35 disabled=True,
- 36 )
- 37 gui_next_frame = server.gui.add_button("Next Frame", disabled=True)
- 38 gui_prev_frame = server.gui.add_button("Prev Frame", disabled=True)
- 39 gui_playing = server.gui.add_checkbox("Playing", True)
- 40 gui_framerate = server.gui.add_slider(
- 41 "FPS", min=1, max=60, step=0.1, initial_value=loader.fps
+ 12def main(
+ 13 data_path: Path = Path(__file__).parent / "assets/record3d_dance",
+ 14 downsample_factor: int = 4,
+ 15 max_frames: int = 100,
+ 16 share: bool = False,
+ 17) -> None:
+ 18 server = viser.ViserServer()
+ 19 if share:
+ 20 server.request_share_url()
+ 21
+ 22 print("Loading frames!")
+ 23 loader = viser.extras.Record3dLoader(data_path)
+ 24 num_frames = min(max_frames, loader.num_frames())
+ 25
+ 26 # Add playback UI.
+ 27 with server.gui.add_folder("Playback"):
+ 28 gui_point_size = server.gui.add_slider(
+ 29 "Point size",
+ 30 min=0.001,
+ 31 max=0.02,
+ 32 step=1e-3,
+ 33 initial_value=0.01,
+ 34 )
+ 35 gui_timestep = server.gui.add_slider(
+ 36 "Timestep",
+ 37 min=0,
+ 38 max=num_frames - 1,
+ 39 step=1,
+ 40 initial_value=0,
+ 41 disabled=True,
42 )
- 43 gui_framerate_options = server.gui.add_button_group(
- 44 "FPS options", ("10", "20", "30", "60")
- 45 )
- 46
- 47 # Frame step buttons.
- 48 @gui_next_frame.on_click
- 49 def _(_) -> None:
- 50 gui_timestep.value = (gui_timestep.value + 1) % num_frames
- 51
- 52 @gui_prev_frame.on_click
- 53 def _(_) -> None:
- 54 gui_timestep.value = (gui_timestep.value - 1) % num_frames
- 55
- 56 # Disable frame controls when we're playing.
- 57 @gui_playing.on_update
- 58 def _(_) -> None:
- 59 gui_timestep.disabled = gui_playing.value
- 60 gui_next_frame.disabled = gui_playing.value
- 61 gui_prev_frame.disabled = gui_playing.value
- 62
- 63 # Set the framerate when we click one of the options.
- 64 @gui_framerate_options.on_click
- 65 def _(_) -> None:
- 66 gui_framerate.value = int(gui_framerate_options.value)
- 67
- 68 prev_timestep = gui_timestep.value
- 69
- 70 # Toggle frame visibility when the timestep slider changes.
- 71 @gui_timestep.on_update
- 72 def _(_) -> None:
- 73 nonlocal prev_timestep
- 74 current_timestep = gui_timestep.value
- 75 with server.atomic():
- 76 frame_nodes[current_timestep].visible = True
- 77 frame_nodes[prev_timestep].visible = False
- 78 prev_timestep = current_timestep
- 79 server.flush() # Optional!
- 80
- 81 # Load in frames.
- 82 server.scene.add_frame(
- 83 "/frames",
- 84 wxyz=tf.SO3.exp(np.array([np.pi / 2.0, 0.0, 0.0])).wxyz,
- 85 position=(0, 0, 0),
- 86 show_axes=False,
- 87 )
- 88 frame_nodes: list[viser.FrameHandle] = []
- 89 for i in tqdm(range(num_frames)):
- 90 frame = loader.get_frame(i)
- 91 position, color = frame.get_point_cloud(downsample_factor)
- 92
- 93 # Add base frame.
- 94 frame_nodes.append(server.scene.add_frame(f"/frames/t{i}", show_axes=False))
- 95
- 96 # Place the point cloud in the frame.
- 97 server.scene.add_point_cloud(
- 98 name=f"/frames/t{i}/point_cloud",
- 99 points=position,
-100 colors=color,
-101 point_size=0.01,
-102 point_shape="rounded",
-103 )
-104
-105 # Place the frustum.
-106 fov = 2 * np.arctan2(frame.rgb.shape[0] / 2, frame.K[0, 0])
-107 aspect = frame.rgb.shape[1] / frame.rgb.shape[0]
-108 server.scene.add_camera_frustum(
-109 f"/frames/t{i}/frustum",
-110 fov=fov,
-111 aspect=aspect,
-112 scale=0.15,
-113 image=frame.rgb[::downsample_factor, ::downsample_factor],
-114 wxyz=tf.SO3.from_matrix(frame.T_world_camera[:3, :3]).wxyz,
-115 position=frame.T_world_camera[:3, 3],
+ 43 gui_next_frame = server.gui.add_button("Next Frame", disabled=True)
+ 44 gui_prev_frame = server.gui.add_button("Prev Frame", disabled=True)
+ 45 gui_playing = server.gui.add_checkbox("Playing", True)
+ 46 gui_framerate = server.gui.add_slider(
+ 47 "FPS", min=1, max=60, step=0.1, initial_value=loader.fps
+ 48 )
+ 49 gui_framerate_options = server.gui.add_button_group(
+ 50 "FPS options", ("10", "20", "30", "60")
+ 51 )
+ 52
+ 53 # Frame step buttons.
+ 54 @gui_next_frame.on_click
+ 55 def _(_) -> None:
+ 56 gui_timestep.value = (gui_timestep.value + 1) % num_frames
+ 57
+ 58 @gui_prev_frame.on_click
+ 59 def _(_) -> None:
+ 60 gui_timestep.value = (gui_timestep.value - 1) % num_frames
+ 61
+ 62 # Disable frame controls when we're playing.
+ 63 @gui_playing.on_update
+ 64 def _(_) -> None:
+ 65 gui_timestep.disabled = gui_playing.value
+ 66 gui_next_frame.disabled = gui_playing.value
+ 67 gui_prev_frame.disabled = gui_playing.value
+ 68
+ 69 # Set the framerate when we click one of the options.
+ 70 @gui_framerate_options.on_click
+ 71 def _(_) -> None:
+ 72 gui_framerate.value = int(gui_framerate_options.value)
+ 73
+ 74 prev_timestep = gui_timestep.value
+ 75
+ 76 # Toggle frame visibility when the timestep slider changes.
+ 77 @gui_timestep.on_update
+ 78 def _(_) -> None:
+ 79 nonlocal prev_timestep
+ 80 current_timestep = gui_timestep.value
+ 81 with server.atomic():
+ 82 # Update point size.
+ 83 point_nodes[current_timestep].point_size = gui_point_size.value
+ 84
+ 85 # Toggle visibility.
+ 86 frame_nodes[current_timestep].visible = True
+ 87 frame_nodes[prev_timestep].visible = False
+ 88 prev_timestep = current_timestep
+ 89 server.flush() # Optional!
+ 90
+ 91 # Load in frames.
+ 92 server.scene.add_frame(
+ 93 "/frames",
+ 94 wxyz=tf.SO3.exp(np.array([np.pi / 2.0, 0.0, 0.0])).wxyz,
+ 95 position=(0, 0, 0),
+ 96 show_axes=False,
+ 97 )
+ 98 frame_nodes: list[viser.FrameHandle] = []
+ 99 point_nodes: list[viser.PointCloudHandle] = []
+100 for i in tqdm(range(num_frames)):
+101 frame = loader.get_frame(i)
+102 position, color = frame.get_point_cloud(downsample_factor)
+103
+104 # Add base frame.
+105 frame_nodes.append(server.scene.add_frame(f"/frames/t{i}", show_axes=False))
+106
+107 # Place the point cloud in the frame.
+108 point_nodes.append(
+109 server.scene.add_point_cloud(
+110 name=f"/frames/t{i}/point_cloud",
+111 points=position,
+112 colors=color,
+113 point_size=gui_point_size.value,
+114 point_shape="rounded",
+115 )
116 )
117
-118 # Add some axes.
-119 server.scene.add_frame(
-120 f"/frames/t{i}/frustum/axes",
-121 axes_length=0.05,
-122 axes_radius=0.005,
-123 )
-124
-125 # Hide all but the current frame.
-126 for i, frame_node in enumerate(frame_nodes):
-127 frame_node.visible = i == gui_timestep.value
-128
-129 # Playback update loop.
-130 prev_timestep = gui_timestep.value
-131 while True:
-132 if gui_playing.value:
-133 gui_timestep.value = (gui_timestep.value + 1) % num_frames
-134
-135 time.sleep(1.0 / gui_framerate.value)
-136
+118 # Place the frustum.
+119 fov = 2 * np.arctan2(frame.rgb.shape[0] / 2, frame.K[0, 0])
+120 aspect = frame.rgb.shape[1] / frame.rgb.shape[0]
+121 server.scene.add_camera_frustum(
+122 f"/frames/t{i}/frustum",
+123 fov=fov,
+124 aspect=aspect,
+125 scale=0.15,
+126 image=frame.rgb[::downsample_factor, ::downsample_factor],
+127 wxyz=tf.SO3.from_matrix(frame.T_world_camera[:3, :3]).wxyz,
+128 position=frame.T_world_camera[:3, 3],
+129 )
+130
+131 # Add some axes.
+132 server.scene.add_frame(
+133 f"/frames/t{i}/frustum/axes",
+134 axes_length=0.05,
+135 axes_radius=0.005,
+136 )
137
-138if __name__ == "__main__":
-139 tyro.cli(main)
+138 # Hide all but the current frame.
+139 for i, frame_node in enumerate(frame_nodes):
+140 frame_node.visible = i == gui_timestep.value
+141
+142 # Playback update loop.
+143 prev_timestep = gui_timestep.value
+144 while True:
+145 if gui_playing.value:
+146 gui_timestep.value = (gui_timestep.value + 1) % num_frames
+147
+148 time.sleep(1.0 / gui_framerate.value)
+149
+150
+151if __name__ == "__main__":
+152 tyro.cli(main)
Add a model to render using Gaussian Splatting.
+Experimental. This feature is experimental and still under +development. It may be changed or removed.
+name (str) – Scene node name.
centers (ndarray) – Centers of Gaussians. (N, 3).
covariances (ndarray) – Second moment for each Gaussian. (N, 3, 3).
rgbs (ndarray) – Color for each Gaussian. (N, 3).
opacities (ndarray) – Opacity for each Gaussian. (N, 1).
wxyz (Tuple[float, float, float, float] | ndarray) – R_parent_local transformation.
position (Tuple[float, float, float] | ndarray) – t_parent_local transformation.
visibile – Initial visibility of scene node.
visible (bool)
Scene node handle.
+SceneApi.add_mesh_skinned()
SceneApi.add_mesh_simple()
SceneApi.add_mesh_trimesh()
SceneApi.add_gaussian_splats()
SceneApi.add_box()
SceneApi.add_icosphere()
SceneApi.set_background_image()