|
"""Record3D visualizer |
|
""" |
|
|
|
import time |
|
from decord import VideoReader, cpu |
|
|
|
import numpy as np |
|
import tyro |
|
import viser |
|
import viser.extras |
|
import viser.transforms as tf |
|
from tqdm.auto import tqdm |
|
|
|
|
|
def main( |
|
data_path: str, |
|
vid_name: str, |
|
downsample_factor: int = 8, |
|
max_frames: int = 100, |
|
share: bool = False, |
|
point_size=0.01, |
|
) -> None: |
|
|
|
server = viser.ViserServer() |
|
if share: |
|
server.request_share_url() |
|
|
|
print("Loading frames!") |
|
dis_path = data_path + "/" + vid_name + ".npz" |
|
vid_path = data_path + "/" + vid_name + "_input.mp4" |
|
|
|
disp_map = np.load(dis_path)["depth"][:, :, :] |
|
T = disp_map.shape[0] |
|
H = disp_map.shape[1] |
|
W = disp_map.shape[2] |
|
|
|
disp_max = disp_map.max() |
|
disp_min = disp_map.min() |
|
disp_map = (disp_map - disp_min) / (disp_max - disp_min) |
|
|
|
vr = VideoReader(vid_path, ctx=cpu(0)) |
|
vid = vr[:].asnumpy()[:, 0:H, 0:W] |
|
fps = vr.get_avg_fps() |
|
num_frames = min(max_frames, T) |
|
|
|
|
|
with server.gui.add_folder("Playback"): |
|
gui_timestep = server.gui.add_slider( |
|
"Timestep", |
|
min=0, |
|
max=num_frames - 1, |
|
step=1, |
|
initial_value=0, |
|
disabled=True, |
|
) |
|
gui_next_frame = server.gui.add_button("Next Frame", disabled=True) |
|
gui_prev_frame = server.gui.add_button("Prev Frame", disabled=True) |
|
gui_playing = server.gui.add_checkbox("Playing", True) |
|
gui_framerate = server.gui.add_slider( |
|
"FPS", min=1, max=60, step=0.1, initial_value=fps |
|
) |
|
gui_framerate_options = server.gui.add_button_group( |
|
"FPS options", ("10", "20", "30", "60") |
|
) |
|
|
|
|
|
@gui_next_frame.on_click |
|
def _(_) -> None: |
|
gui_timestep.value = (gui_timestep.value + 1) % num_frames |
|
|
|
@gui_prev_frame.on_click |
|
def _(_) -> None: |
|
gui_timestep.value = (gui_timestep.value - 1) % num_frames |
|
|
|
|
|
@gui_playing.on_update |
|
def _(_) -> None: |
|
gui_timestep.disabled = gui_playing.value |
|
gui_next_frame.disabled = gui_playing.value |
|
gui_prev_frame.disabled = gui_playing.value |
|
|
|
|
|
@gui_framerate_options.on_click |
|
def _(_) -> None: |
|
gui_framerate.value = int(gui_framerate_options.value) |
|
|
|
prev_timestep = gui_timestep.value |
|
|
|
|
|
@gui_timestep.on_update |
|
def _(_) -> None: |
|
nonlocal prev_timestep |
|
current_timestep = gui_timestep.value |
|
with server.atomic(): |
|
frame_nodes[current_timestep].visible = True |
|
frame_nodes[prev_timestep].visible = False |
|
prev_timestep = current_timestep |
|
server.flush() |
|
|
|
|
|
server.scene.add_frame( |
|
"/frames", |
|
wxyz=tf.SO3.exp(np.array([0.0, 0.0, 0.0])).wxyz, |
|
position=(0, 0, 0), |
|
show_axes=False, |
|
) |
|
frame_nodes: list[viser.FrameHandle] = [] |
|
for i in tqdm(range(num_frames)): |
|
|
|
|
|
frame_nodes.append(server.scene.add_frame(f"/frames/t{i}", show_axes=False)) |
|
|
|
position_image = np.where(np.zeros([H, W]) == 0) |
|
v = np.array(position_image[0]) |
|
u = np.array(position_image[1]) |
|
d = disp_map[i, v, u] |
|
|
|
zc = 1.0 / (d + 0.1) |
|
|
|
|
|
xc = zc * (u - (W / 2.0)) / (W / 2.0) |
|
yc = zc * (v - (H / 2.0)) / (H / 2.0) |
|
|
|
zc -= 4 |
|
|
|
points = np.stack((xc, yc, zc), axis=1) |
|
colors = vid[i, v, u] |
|
|
|
points = points[::downsample_factor] |
|
colors = colors[::downsample_factor] |
|
|
|
|
|
server.scene.add_point_cloud( |
|
name=f"/frames/t{i}/point_cloud", |
|
points=points, |
|
colors=colors, |
|
point_size=point_size, |
|
point_shape="rounded", |
|
) |
|
|
|
|
|
for i, frame_node in enumerate(frame_nodes): |
|
frame_node.visible = i == gui_timestep.value |
|
|
|
|
|
prev_timestep = gui_timestep.value |
|
while True: |
|
if gui_playing.value: |
|
gui_timestep.value = (gui_timestep.value + 1) % num_frames |
|
|
|
time.sleep(1.0 / gui_framerate.value) |
|
|
|
|
|
if __name__ == "__main__": |
|
tyro.cli( |
|
main( |
|
|
|
data_path="./demo_output", |
|
|
|
vid_name="example_01", |
|
|
|
downsample_factor=8, |
|
|
|
point_size=0.007, |
|
) |
|
) |
|
|