Skip to content

Commit 8896ed0

Browse files
authored
Clean up the fvdb viewer API (#257)
The viewer API now has a single server and an API to support multiple scenes (though these need to be wired through the nanovdb-editor API). It looks something like: ``` import fvdb # Start the server, if one is already runing, this is a No-op and will print a warning fvdb.viz.init(ip_address="127.0.0.1", port=8000) # Create a scene to visualize scene1 = fvdb.viz.Scene(name="My scene") scene1.add_gaussian_splat_3d(splats, ...) scene1.add_cameras(...) scene2 = fvdb.viz.get_scene("My other scene") scene2.add_point_cloud(...) fvdb.viz.show() ``` --------- Signed-off-by: Francis Williams <francis@fwilliams.info> Signed-off-by: Francis Williams <fwilliams@users.noreply.github.com>
1 parent 5d09154 commit 8896ed0

File tree

12 files changed

+623
-288
lines changed

12 files changed

+623
-288
lines changed

fvdb/_Cpp.pyi

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,9 @@ class Viewer:
969969
def __init__(self, ip_address: str, port: int, verbose: bool) -> None: ...
970970
def port(self) -> int: ...
971971
def ip_address(self) -> str: ...
972-
def add_gaussian_splat_3d(self, name: str, gaussian_splat_3d: GaussianSplat3d) -> GaussianSplat3dView: ...
972+
def add_gaussian_splat_3d_view(self, name: str, gaussian_splat_3d: GaussianSplat3d) -> GaussianSplat3dView: ...
973+
def has_gaussian_splat_3d_view(self, name: str) -> bool: ...
974+
def get_gaussian_splat_3d_view(self, name: str) -> GaussianSplat3dView: ...
973975
def camera_orbit_center(self) -> tuple[float, float, float]: ...
974976
def set_camera_orbit_center(self, ox: float, oy: float, oz: float) -> None: ...
975977
def camera_orbit_radius(self) -> float: ...
@@ -993,6 +995,8 @@ class Viewer:
993995
frustum_near_plane: float = 0.1,
994996
frustum_far_plane: float = 1000.0,
995997
) -> CameraView: ...
998+
def has_camera_view(self, name: str) -> bool: ...
999+
def get_camera_view(self, name: str) -> CameraView: ...
9961000

9971001
class config:
9981002
enable_ultra_sparse_acceleration: ClassVar[bool] = ...

fvdb/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,11 @@ def _parse_device_string(device_or_device_string: str | torch.device) -> torch.d
8989

9090
# The following import needs to come after the GridBatch and JaggedTensor imports
9191
# immediately above in order to avoid a circular dependency error.
92-
from . import nn
92+
# Make these available without an explicit submodule import
93+
from . import nn, viz, utils
9394

9495
# isort: on
9596

96-
# Make these available without an explicit submodule import
97-
from . import utils, viz
98-
9997

10098
def jcat(things_to_cat, dim=None):
10199
if len(things_to_cat) == 0:

fvdb/viz/__init__.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
# Copyright Contributors to the OpenVDB Project
22
# SPDX-License-Identifier: Apache-2.0
33
#
4+
from ._camera_view import CamerasView
5+
from ._gaussian_splat_3d_view import GaussianSplat3dView, ShOrderingMode
6+
from ._point_cloud_view import PointCloudView
7+
from ._scene import Scene, get_scene
48
from ._utils import grid_edge_network, gridbatch_edge_network
5-
from ._viewer import CameraView, GaussianSplat3dView, Viewer
9+
from ._viewer_server import init, show
610

711
__all__ = [
8-
"Viewer",
12+
"init",
13+
"show",
914
"GaussianSplat3dView",
10-
"CameraView",
15+
"CamerasView",
16+
"get_scene",
17+
"ShOrderingMode",
18+
"PointCloudView",
19+
"Scene",
1120
"grid_edge_network",
1221
"gridbatch_edge_network",
1322
]

fvdb/viz/_camera_view.py

Lines changed: 136 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,91 +8,171 @@
88

99
from .._Cpp import CameraView as CameraViewCpp
1010
from ..types import NumericMaxRank1, NumericScalarNative, to_Vec3f
11+
from ._viewer_server import _get_viewer_server_cpp
1112

1213

13-
class CameraView:
14+
class CamerasView:
1415
"""
15-
A view for a set of camera frusta and axes in a `Viewer` with parameters to adjust how the cameras are rendered.
16+
A view for a set of camera frusta and axes in a :class:`fvdb.viz.Scene` with parameters to
17+
adjust how the cameras are rendered.
1618
17-
Each camera is represented by its camera-to-world and projection matrices, and drawn as a wireframe frustum
18-
with orthogonal axes at the camera origin.
19+
Each camera is represented by its camera-to-world and projection matrices, and drawn as a
20+
wireframe frustum with orthogonal axes at the camera's origin.
1921
"""
2022

2123
__PRIVATE__ = object()
2224

23-
def __init__(self, view: CameraViewCpp, _private: Any = None):
25+
def _get_view(self) -> CameraViewCpp:
2426
"""
25-
Create a new `CameraView` from a C++ implementation. This constructor is private and should not be called directly.
26-
Use `Viewer.add_camera_view()` instead.
27+
Get the underlying C++ CameraView instance from the viewer server or raise
28+
a :class:`RuntimeError` if it is not registered.
29+
30+
Returns:
31+
view (CameraViewCpp): The C++ CameraView instance
32+
"""
33+
server = _get_viewer_server_cpp()
34+
35+
if not server.has_camera_view(self._name):
36+
raise RuntimeError(f"CameraView '{self._name}' is not registered with the viewer server.")
37+
return server.get_camera_view(self._name)
38+
39+
def __init__(
40+
self,
41+
scene_name: str,
42+
name: str,
43+
camera_to_world_matrices: torch.Tensor,
44+
projection_matrices: torch.Tensor,
45+
image_sizes: torch.Tensor,
46+
axis_length: float,
47+
axis_thickness: float,
48+
frustum_line_width: float,
49+
frustum_scale: float,
50+
frustum_color: tuple[float, float, float],
51+
frustum_near_plane: float,
52+
frustum_far_plane: float,
53+
enabled: bool,
54+
_private: Any = None,
55+
):
56+
"""
57+
Create a new :class:`CamerasView` or update an existing one within a scene with the given name.
58+
59+
.. warning::
60+
61+
This constructor is private and should never be called directly. Use :meth:`fvdb.viz.Scene.add_cameras()` instead.
62+
63+
Args:
64+
scene_name (str): The name of the scene the view belongs to.
65+
name (str): The name of the :class:`CamerasView`.
66+
camera_to_world_matrices (torch.Tensor): A tensor of shape ``(N, 4, 4)`` representing the camera-to-world matrices for N cameras.
67+
projection_matrices (torch.Tensor): A tensor of shape ``(N, 4, 4)`` representing the projection matrices for N cameras.
68+
image_sizes (torch.Tensor): A tensor of shape ``(N, 2)`` representing the image sizes (width, height) for N cameras.
69+
axis_length (float): The length of the axes drawn at each camera origin in world units.
70+
axis_thickness (float): The thickness of the axes drawn at each camera origin in pixel units.
71+
frustum_line_width (float): The line width of the frustum in pixel units.
72+
frustum_scale (float): The scale factor applied to the frustum visualization.
73+
frustum_color (tuple[float, float, float]): The color of the frustum lines as an RGB tuple with values in [0, 1].
74+
frustum_near_plane (float): The near plane distance for the camera frusta.
75+
frustum_far_plane (float): The far plane distance for the camera frusta.
76+
enabled (bool): Whether the camera frusta and axes are shown in the viewer.
77+
_private (Any): A private object to prevent direct construction. Must be :attr:`CamerasView.__PRIVATE__`.
2778
"""
28-
self._view = view
2979
if _private is not self.__PRIVATE__:
30-
raise ValueError("CameraView constructor is private. Use Viewer.add_camera_view() instead.")
80+
raise ValueError("CameraView constructor is private. Use Scene.add_cameras() instead.")
81+
82+
server = _get_viewer_server_cpp()
83+
self._scene_name = scene_name
84+
self._name = name
85+
86+
view: CameraViewCpp = server.add_camera_view(
87+
name=name,
88+
camera_to_world_matrices=camera_to_world_matrices,
89+
projection_matrices=projection_matrices,
90+
image_sizes=image_sizes,
91+
frustum_near_plane=frustum_near_plane,
92+
frustum_far_plane=frustum_far_plane,
93+
)
94+
95+
view.axis_length = axis_length
96+
view.axis_thickness = axis_thickness
97+
view.frustum_line_width = frustum_line_width
98+
view.frustum_scale = frustum_scale
99+
view.frustum_color = frustum_color
100+
view.visible = enabled
31101

32102
@property
33-
def visible(self) -> bool:
103+
def enabled(self) -> bool:
34104
"""
35-
Get whether the camera frusta and axes are shown in the viewer.
105+
Return whether the camera frusta and axes are shown in the scene.
36106
37107
Returns:
38-
bool: True if the camera frusta and axes are visible, False otherwise.
108+
enabled (bool): ``True`` if the camera frusta and axes are shown in the
109+
scene, ``False`` otherwise.
39110
"""
40-
return self._view.visible
111+
view = self._get_view()
112+
return view.visible
41113

42-
@visible.setter
43-
def visible(self, visible: bool):
114+
@enabled.setter
115+
def enabled(self, enabled: bool):
44116
"""
45-
Set whether the camera frusta and axes are shown in the viewer.
117+
Set whether the camera frusta and axes are shown in the scene.
118+
46119
Args:
47-
visible (bool): True to show the camera frusta and axes, False to hide them
120+
enabled (bool): ``True`` to show the camera frusta and axes, ``False`` to hide them.
48121
"""
49-
self._view.visible = visible
122+
view = self._get_view()
123+
view.visible = enabled
50124

51125
@property
52126
def axis_length(self) -> float:
53127
"""
54128
Get the length of the axes drawn at each camera origin in world units.
55129
56130
Returns:
57-
float: The length of the axes.
131+
length (float): The length of the axes.
58132
"""
59-
return self._view.axis_length
133+
view = self._get_view()
134+
return view.axis_length
60135

61136
@axis_length.setter
62137
def axis_length(self, length: NumericScalarNative):
63138
"""
64139
Set the length of the axes drawn at each camera origin in world units.
140+
65141
Args:
66142
length (NumericScalarNative): The length of the axes.
67143
"""
68-
self._view.axis_length = float(length)
144+
view = self._get_view()
145+
view.axis_length = float(length)
69146

70147
@property
71148
def axis_thickness(self) -> float:
72149
"""
73-
Get the thickness of the axes drawn at each camera origin in world units.
150+
Get the thickness of the axes drawn at each camera origin in pixel units.
74151
75152
Returns:
76-
float: The thickness of the axes.
153+
thickness (float): The thickness of the axes.
77154
"""
78-
return self._view.axis_thickness
155+
view = self._get_view()
156+
return view.axis_thickness
79157

80158
@axis_thickness.setter
81159
def axis_thickness(self, thickness: NumericScalarNative):
82160
"""
83-
Set the thickness of the axes drawn at each camera origin in world units.
161+
Set the thickness of the axes drawn at each camera origin in pixel units.
84162
85163
Args:
86164
thickness (NumericScalarNative): The thickness of the axes.
87165
"""
88-
self._view.axis_thickness = float(thickness)
166+
view = self._get_view()
167+
view.axis_thickness = float(thickness)
89168

90169
@property
91170
def frustum_line_width(self) -> float:
92171
"""
93172
Get the line width of the frustum in the camera frustum view.
94173
"""
95-
return self._view.frustum_line_width
174+
view = self._get_view()
175+
return view.frustum_line_width
96176

97177
@frustum_line_width.setter
98178
def frustum_line_width(self, width: NumericScalarNative):
@@ -102,46 +182,63 @@ def frustum_line_width(self, width: NumericScalarNative):
102182
Args:
103183
width (NumericScalarNative): The line width of the frustum in world units.
104184
"""
105-
self._view.frustum_line_width = float(width)
185+
view = self._get_view()
186+
view.frustum_line_width = float(width)
106187

107188
@property
108189
def frustum_scale(self) -> float:
109190
"""
110-
Get the scale factor applied to the frustum visualization.
191+
Get the scale factor applied to the frustum visualization. Each frustum will have its size
192+
multiplied by this scale factor when rendered.
193+
194+
*E.g.* if the frustum has ``near = 0.1``, and ``far = 1.0``, then setting the frustum
195+
scale to ``2.0`` will render the frustum as if ``near = 0.2`` and ``far = 2.0``.
196+
197+
Returns:
198+
scale (float): The scale factor applied to the frustum visualization.
111199
"""
112-
return self._view.frustum_scale
200+
view = self._get_view()
201+
return view.frustum_scale
113202

114203
@frustum_scale.setter
115204
def frustum_scale(self, scale: NumericScalarNative):
116205
"""
117-
Set the scale factor applied to the frustum visualization.
206+
Set the scale factor applied to the frustum visualization. Each frustum will have its size
207+
multiplied by this scale factor when rendered.
208+
209+
*E.g.* if the frustum has ``near = 0.1``, and ``far = 1.0``, then setting the frustum
210+
scale to ``2.0`` will render the frustum as if ``near = 0.2`` and ``far = 2.0``.
118211
119212
Args:
120213
scale (NumericScalarNative): The scale factor to apply to the frustum visualization.
121214
"""
122-
self._view.frustum_scale = float(scale)
215+
view = self._get_view()
216+
view.frustum_scale = float(scale)
123217

124218
@property
125219
def frustum_color(self) -> torch.Tensor:
126220
"""
127-
Get the color of the frustum lines as a tensor of shape (3,) with values in [0, 1].
221+
Get the RGB color of the frustum lines as a tensor of shape ``(3,)`` with
222+
values in ``[0, 1]``.
128223
129224
Returns:
130-
torch.Tensor: The color of the frustum lines.
225+
torch.Tensor: The RGB color of the frustum lines.
131226
"""
132-
r, g, b = self._view.frustum_color
227+
view = self._get_view()
228+
r, g, b = view.frustum_color
133229
return torch.tensor([r, g, b], dtype=torch.float32)
134230

135231
@frustum_color.setter
136232
def frustum_color(self, color: NumericMaxRank1):
137233
"""
138-
Set the color of the frustum lines.
234+
Set the RGB color of the frustum lines. Color values must be in ``[0, 1]``.
139235
140236
Args:
141-
color (NumericMaxRank1): A tensor-like object of shape (3,) representing the color of the frustum lines
142-
with values in [0, 1].
237+
color (NumericMaxRank1): A tensor-like object of shape ``(3,)`` representing the
238+
RGB color of the frustum lines with values in ``[0, 1]``.
143239
"""
240+
view = self._get_view()
144241
color_vec3f = to_Vec3f(color).cpu().numpy().tolist()
145242
if any(c < 0.0 or c > 1.0 for c in color_vec3f):
146243
raise ValueError(f"Frustum color components must be in [0, 1], got {color_vec3f}")
147-
self._view.frustum_color = tuple(color_vec3f)
244+
view.frustum_color = tuple(color_vec3f)

0 commit comments

Comments
 (0)