vuer-cli 0.0.4__py3-none-any.whl → 0.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,41 +1,22 @@
1
- """Minimap visualization with GLB model, camera frustums, and depth point clouds."""
1
+ """Minimap visualization with GLB model and depth point clouds."""
2
2
 
3
+ import asyncio
3
4
  import warnings
4
5
  from dataclasses import dataclass
5
6
  from pathlib import Path
6
7
  from textwrap import dedent
7
8
  from typing import List, Optional
8
9
 
10
+ from vuer.schemas import (
11
+ DepthPointCloudProvider,
12
+ InPlaceDepthPointCloud,
13
+ )
14
+
9
15
  from ..utils import print_error
10
16
 
11
17
  warnings.filterwarnings("ignore", category=RuntimeWarning)
12
18
 
13
19
 
14
- def create_camera_positions_around_center(radius=1.0, height=0.0):
15
- """Create four camera positions around a center point at 90-degree intervals."""
16
- import numpy as np
17
-
18
- positions = []
19
- for angle in [0, np.pi / 2, np.pi, 3 * np.pi / 2]:
20
- x = radius * np.cos(angle)
21
- y = radius * np.sin(angle)
22
- z = height
23
- positions.append([x, y, z])
24
- return positions
25
-
26
-
27
- def create_camera_rotations_looking_at_center():
28
- """Create rotations for cameras to look at the center point."""
29
- import numpy as np
30
-
31
- return [
32
- [0, -np.pi / 2, np.pi / 2],
33
- [np.pi / 2, 0, 0],
34
- [-np.pi / 2, np.pi / 2, 0],
35
- [np.pi / 2, -np.pi, np.pi],
36
- ]
37
-
38
-
39
20
  def collect_depth_frames(
40
21
  data_dir: str,
41
22
  camera_names: Optional[List[str]] = None,
@@ -107,15 +88,23 @@ def collect_depth_frames(
107
88
  flip_xy = np.array([[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
108
89
  t_final = flip_xy @ t_ref
109
90
 
110
- # Extract position and rotation
91
+ # Extract camera position and rotation
111
92
  position = t_final[:3, 3].tolist()
112
93
 
113
- # Convert rotation matrix to euler angles (XYZ order)
114
94
  from scipy.spatial.transform import Rotation
115
95
 
116
96
  rot_matrix = t_final[:3, :3]
117
97
  rotation = Rotation.from_matrix(rot_matrix).as_euler("xyz").tolist()
118
98
 
99
+ # Compute baselink transform (robot base, not camera)
100
+ t_base_ref = np.linalg.inv(first_camera_transform) @ t_world_to_base
101
+ t_base_final = flip_xy @ t_base_ref
102
+ baselink_position = t_base_final[:3, 3].tolist()
103
+ baselink_rot_matrix = t_base_final[:3, :3]
104
+ baselink_rotation = (
105
+ Rotation.from_matrix(baselink_rot_matrix).as_euler("xyz").tolist()
106
+ )
107
+
119
108
  frames.append(
120
109
  {
121
110
  "depth_file": pair["depth_file"],
@@ -123,6 +112,8 @@ def collect_depth_frames(
123
112
  "timestamp_s": pair["timestamp_s"],
124
113
  "position": position,
125
114
  "rotation": rotation,
115
+ "baselink_position": baselink_position,
116
+ "baselink_rotation": baselink_rotation,
126
117
  "intrinsics": generator.intrinsics,
127
118
  }
128
119
  )
@@ -133,111 +124,48 @@ def collect_depth_frames(
133
124
  return frames
134
125
 
135
126
 
136
- def create_proxie_group(
137
- group_id: int,
138
- initial_position=None,
139
- initial_rotation=None,
140
- opacity: float = 1.0,
141
- glb_url: str = None,
142
- ):
143
- """Create a group containing a GLB model with four camera frustums."""
144
- from vuer.schemas import Frustum, Glb, Group
145
-
146
- if initial_position is None:
147
- initial_position = [0, 0, 0]
148
- if initial_rotation is None:
149
- initial_rotation = [0, 0, 0]
150
-
151
- camera_positions = create_camera_positions_around_center(radius=0.1, height=1.625)
152
- camera_rotations = create_camera_rotations_looking_at_center()
153
-
154
- frustums = []
155
- for i, (pos, rot) in enumerate(zip(camera_positions, camera_rotations, strict=False)):
156
- offsets = {0: (0.20, 0.008), 1: (0.17, -0.025), 2: (0.18, 0.005), 3: (0.185, 0.02)}
157
- if i in offsets:
158
- pos[0] += offsets[i][0]
159
- pos[1] += offsets[i][1]
160
-
161
- frustums.append(
162
- Frustum(
163
- key=f"frustum-{group_id}-{i}",
164
- position=pos,
165
- rotation=rot,
166
- scale=0.1,
167
- fov=60,
168
- aspect=16 / 9,
169
- near=0.1,
170
- far=0.2,
171
- showFrustum=True,
172
- showImagePlane=True,
173
- showFocalPlane=False,
174
- colorFrustum="cyan",
175
- )
176
- )
177
-
178
- initial_position[0] += 0
179
- initial_position[1] += 2
180
- initial_position[2] += 0
181
- initial_rotation[0] += -1.57
182
-
183
- return Group(
184
- Glb(
185
- src=glb_url,
186
- position=[0, 0, 0],
187
- rotation=[1.57, 0, 0],
188
- scale=0.01,
189
- opacity=opacity,
190
- key=f"model-{group_id}",
191
- ),
192
- *frustums,
193
- position=initial_position,
194
- rotation=initial_rotation,
195
- scale=1.0,
196
- key=f"group-{group_id}",
197
- )
198
-
199
-
200
127
  async def main_viz(
201
128
  sess,
202
- workspace,
129
+ app,
130
+ scene_store,
203
131
  depth_frames: List[dict],
204
132
  cmap: str = "turbo",
205
133
  color_mode: str = "depth",
206
134
  fov: float = 58.0,
207
135
  max_depth: float = 10.0,
208
- num_groups: int = 3,
209
- group_spacing: float = 2.0,
210
- base_position: Optional[List[float]] = None,
211
136
  glb_url: str = None,
137
+ fps: float = 10.0,
138
+ loop: bool = True,
212
139
  ):
213
- """Main visualization with DepthPointCloud components."""
214
- import cv2
140
+ """Main visualization with frame-by-frame depth point cloud and robot model playback."""
141
+ import math
215
142
 
216
- from vuer.schemas import DepthPointCloud, DepthPointCloudProvider, Scene
143
+ from vuer.schemas import Glb
217
144
 
218
- # png encoder for depth images
219
- def png_encode(image):
220
- """Encode image as PNG bytes."""
221
- _, buffer = cv2.imencode(".png", image)
222
- return buffer.tobytes()
145
+ print("Setting up scene...")
223
146
 
224
- print("Loading scene...")
147
+ # Link all depth files upfront
148
+ for i, frame in enumerate(depth_frames):
149
+ depth_filename = f"depth/{i}.png"
150
+ app.workspace.link(Path(frame["depth_file"]), f"/{depth_filename}")
225
151
 
226
- # Create virtual file endpoints for each depth frame
227
- depth_point_clouds = []
152
+ # Construct proper GLB URL using workspace prefix
153
+ glb_full_url = str(app.localhost_prefix / glb_url)
228
154
 
229
- for i, frame in enumerate(depth_frames):
230
- depth_path = f"/depth/{i}.png"
155
+ # clear the default scene (removes lighting / grid)
156
+ # scene_store.set @ Scene(up=[0, 1, 0])
157
+ # Set initial scene from the scene store
158
+ # sess.upsert @ scene_store.scene
159
+ await asyncio.sleep(0.01)
231
160
 
232
- # Link the depth file to a virtual endpoint
233
- def make_loader(frame_info):
234
- def load_depth():
235
- depth = cv2.imread(str(frame_info["depth_file"]), cv2.IMREAD_UNCHANGED)
236
- return png_encode(depth)
161
+ frame_delay = 1.0 / fps
162
+ frame_idx = 0
237
163
 
238
- return load_depth
164
+ print(f"Starting playback: {len(depth_frames)} frames at {fps} FPS (loop={loop})")
239
165
 
240
- workspace.link(make_loader(frame), depth_path)
166
+ while True:
167
+ frame = depth_frames[frame_idx]
168
+ depth_filename = f"depth/{frame_idx}.png"
241
169
 
242
170
  # Compute FOV from intrinsics if available
243
171
  frame_fov = fov
@@ -245,56 +173,47 @@ async def main_viz(
245
173
  fx = frame["intrinsics"].get("fx", None)
246
174
  height = frame["intrinsics"].get("height", None)
247
175
  if fx and height:
248
- import numpy as np
249
-
250
- frame_fov = 2 * np.arctan(height / (2 * fx)) * 180 / np.pi
251
-
252
- depth_point_clouds.append(
253
- DepthPointCloud(
254
- key=f"depth-pc-{i}",
255
- depth=depth_path,
256
- cmap=cmap,
257
- colorMode=color_mode,
258
- fov=frame_fov,
259
- position=frame["position"],
260
- rotation=frame["rotation"],
261
- maxDepth=max_depth,
262
- )
263
- )
264
-
265
- print(f"Created {len(depth_point_clouds)} depth point clouds")
266
-
267
- # Create robot model groups
268
- if num_groups > 1:
269
- opacity_values = [1.0 - (0.3 * i / (num_groups - 1)) for i in range(num_groups)]
270
- else:
271
- opacity_values = [1.0]
272
-
273
- groups = [
274
- create_proxie_group(
275
- i,
276
- [base_position[0], base_position[1], base_position[2] - i * group_spacing],
277
- [0, 0, 0],
278
- opacity_values[i],
279
- glb_url=glb_url,
176
+ frame_fov = 2 * math.atan(height / (2 * fx)) * 180 / math.pi
177
+
178
+ depth_pc = InPlaceDepthPointCloud(
179
+ key="depth-pc-current",
180
+ depth=app.localhost_prefix / depth_filename,
181
+ cmap=cmap,
182
+ colorMode=color_mode,
183
+ fov=frame_fov,
184
+ position=frame["position"],
185
+ rotation=frame["rotation"],
186
+ maxDepth=max_depth,
280
187
  )
281
- for i in range(num_groups)
282
- ]
283
188
 
284
- # Build scene with DepthPointCloudProvider for high performance
285
- print("Setting up scene...")
286
- sess.upsert @ Scene(
287
- *groups,
288
- DepthPointCloudProvider(
289
- *depth_point_clouds,
189
+ scene_store.upsert @ DepthPointCloudProvider(
190
+ depth_pc,
290
191
  key="depth-provider",
291
192
  frustumCulling=True,
292
- ),
293
- up=[0, 1, 0],
294
- )
193
+ )
194
+
195
+ # Update robot model position based on baselink transform
196
+ baselink_pos = frame["baselink_position"]
197
+ baselink_rot = frame["baselink_rotation"]
198
+
199
+ scene_store.upsert @ Glb(
200
+ src=glb_full_url,
201
+ position=[baselink_pos[0], baselink_pos[1] + 2, baselink_pos[2]],
202
+ rotation=[baselink_rot[0] - 1.57, baselink_rot[1], baselink_rot[2]],
203
+ scale=[0.01, 0.01, 0.01],
204
+ key="proxie-model",
205
+ )
206
+
207
+ await asyncio.sleep(frame_delay)
208
+
209
+ frame_idx += 1
210
+ if frame_idx >= len(depth_frames):
211
+ if loop:
212
+ frame_idx = 0
213
+ else:
214
+ break
295
215
 
296
- print("Ready! Open browser to view.")
297
- await sess.forever()
216
+ print("Playback complete.")
298
217
 
299
218
 
300
219
  @dataclass
@@ -303,13 +222,13 @@ class Minimap:
303
222
 
304
223
  DESCRIPTION
305
224
  Creates an interactive 3D visualization combining a GLB robot model with
306
- camera frustums and depth-based point clouds using DepthPointCloud components.
307
- Uses Workspace virtual files for efficient depth image serving.
225
+ depth-based point clouds using DepthPointCloud components. The robot model
226
+ moves frame-by-frame following the baselink trajectory.
227
+ Uses Workspace path linking for efficient depth image streaming.
308
228
 
309
229
  Features:
310
230
  - High-performance depth point cloud rendering with frustum culling
311
- - Multiple robot model instances with configurable spacing
312
- - Four camera frustums per robot showing sensor coverage
231
+ - Robot model that follows the trajectory frame-by-frame
313
232
  - Colormap support (turbo, viridis, inferno, jet)
314
233
  - Multiple color modes (depth, camZ, camDist, localY, worldY)
315
234
 
@@ -333,9 +252,6 @@ class Minimap:
333
252
  # Sparse sampling for faster loading
334
253
  vuer minimap --data-dir /path/to/data --frame-step 10
335
254
 
336
- # Custom robot model groups
337
- vuer minimap --data-dir /path/to/data --num-groups 5 --group-spacing 1.0
338
-
339
255
  DEPENDENCIES
340
256
  Requires: pip install 'vuer-cli[viz]'
341
257
  """
@@ -347,25 +263,26 @@ class Minimap:
347
263
  max_frames: int = None # Max frames per camera
348
264
  frame_step: int = 5 # Process every Nth frame
349
265
  cmap: str = "turbo" # Colormap (turbo, viridis, inferno, jet)
350
- color_mode: str = "depth" # Color mode (depth, camZ, camDist, localY, worldY)
266
+ color_mode: str = "worldY" # Color mode (depth, camZ, camDist, localY, worldY)
351
267
  fov: float = 58.0 # Default camera FOV (RealSense D435)
352
268
  max_depth: float = 10.0 # Maximum depth in meters
353
269
 
354
270
  # Model options
355
- assets_folder: str = None # Static assets folder for GLB model
356
271
  glb_url: str = "proxie.glb" # GLB model URL
357
- num_groups: int = 10 # Number of robot groups to create
358
- group_spacing: float = 0.5 # Spacing between groups along Z-axis
359
- base_position: str = "-2.0,-2.2,0" # Starting position [x,y,z]
272
+
273
+ # Playback options
274
+ fps: float = 10.0 # Frames per second for playback
275
+ loop: bool = True # Loop playback
360
276
 
361
277
  # Server options
362
278
  host: str = "0.0.0.0"
363
279
  port: int = 8012
364
280
 
365
- def run(self) -> int:
281
+ def __call__(self) -> int:
366
282
  """Execute minimap command."""
367
283
  try:
368
- from vuer import Vuer, Workspace
284
+ from vuer import Vuer
285
+ from vuer.rtc.scene_store import SceneStore
369
286
  except ImportError as e:
370
287
  print_error(
371
288
  f"Missing dependencies for minimap command: {e}\n\n"
@@ -384,8 +301,6 @@ class Minimap:
384
301
  if self.cameras:
385
302
  camera_list = [c.strip() for c in self.cameras.split(",")]
386
303
 
387
- base_pos_list = [float(x.strip()) for x in self.base_position.split(",")]
388
-
389
304
  # Collect depth frames
390
305
  print("Collecting depth frames...")
391
306
  depth_frames = collect_depth_frames(
@@ -401,26 +316,8 @@ class Minimap:
401
316
 
402
317
  print(f"Found {len(depth_frames)} depth frames")
403
318
 
404
- # Create workspace for virtual files
405
- workspace = Workspace()
406
-
407
- # Set up assets folder
408
- assets_folder = self.assets_folder
409
- if not assets_folder:
410
- import vuer_cli
411
-
412
- assets_folder = Path(vuer_cli.__file__).parent.parent.parent / "assets"
413
- if not assets_folder.exists():
414
- assets_folder = None
415
-
416
- if assets_folder:
417
- print(f"Using assets folder: {assets_folder}")
418
- app = Vuer(host=self.host, port=self.port, workspace=assets_folder)
419
- else:
420
- app = Vuer(host=self.host, port=self.port)
421
-
422
- # Merge workspace into app
423
- app.workspace = workspace
319
+ # Use current working directory as workspace
320
+ app = Vuer(host=self.host, port=self.port, workspace=Path.cwd())
424
321
 
425
322
  print(
426
323
  dedent(f"""
@@ -429,25 +326,31 @@ class Minimap:
429
326
  Depth frames: {len(depth_frames)}
430
327
  Colormap: {self.cmap}
431
328
  Color mode: {self.color_mode}
329
+ FPS: {self.fps}
330
+ Loop: {self.loop}
432
331
  """).strip()
433
332
  )
434
333
 
334
+ scene_store = SceneStore()
335
+
435
336
  @app.spawn(start=True)
436
337
  async def main_with_args(sess):
437
338
  print("Session started, loading data...")
438
- await main_viz(
439
- sess,
440
- workspace=workspace,
441
- depth_frames=depth_frames,
442
- cmap=self.cmap,
443
- color_mode=self.color_mode,
444
- fov=self.fov,
445
- max_depth=self.max_depth,
446
- num_groups=self.num_groups,
447
- group_spacing=self.group_spacing,
448
- base_position=base_pos_list,
449
- glb_url=self.glb_url,
450
- )
339
+ async with scene_store.subscribe(sess):
340
+ await main_viz(
341
+ sess,
342
+ app=app,
343
+ scene_store=scene_store,
344
+ depth_frames=depth_frames,
345
+ cmap=self.cmap,
346
+ color_mode=self.color_mode,
347
+ fov=self.fov,
348
+ max_depth=self.max_depth,
349
+ glb_url=self.glb_url,
350
+ fps=self.fps,
351
+ loop=self.loop,
352
+ )
353
+ await sess.forever()
451
354
 
452
355
  return 0
453
356
 
@@ -552,7 +552,7 @@ class VizPtcCams:
552
552
  max_depth: float = 10.0 # Maximum depth in meters
553
553
  no_animation: bool = False # Show complete point cloud at once
554
554
 
555
- def run(self) -> int:
555
+ def __call__(self) -> int:
556
556
  """Execute viz_ptc_cams command."""
557
557
  try:
558
558
  import cv2
@@ -403,7 +403,7 @@ class VizPtcProxie:
403
403
  host: str = "0.0.0.0" # Server host
404
404
  port: int = 8012 # Server port
405
405
 
406
- def run(self) -> int:
406
+ def __call__(self) -> int:
407
407
  """Execute viz_ptc_proxie command."""
408
408
  try:
409
409
  import cv2