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.
- vuer_cli/add.py +66 -68
- vuer_cli/envs_publish.py +335 -309
- vuer_cli/envs_pull.py +177 -170
- vuer_cli/login.py +459 -0
- vuer_cli/main.py +7 -2
- vuer_cli/remove.py +84 -84
- vuer_cli/scripts/demcap.py +19 -15
- vuer_cli/scripts/mcap_playback.py +661 -0
- vuer_cli/scripts/minimap.py +113 -210
- vuer_cli/scripts/viz_ptc_cams.py +1 -1
- vuer_cli/scripts/viz_ptc_proxie.py +1 -1
- vuer_cli/sync.py +314 -308
- vuer_cli/upgrade.py +118 -126
- {vuer_cli-0.0.4.dist-info → vuer_cli-0.0.6.dist-info}/METADATA +38 -9
- vuer_cli-0.0.6.dist-info/RECORD +22 -0
- vuer_cli/scripts/vuer_ros_bridge.py +0 -210
- vuer_cli-0.0.4.dist-info/RECORD +0 -21
- {vuer_cli-0.0.4.dist-info → vuer_cli-0.0.6.dist-info}/WHEEL +0 -0
- {vuer_cli-0.0.4.dist-info → vuer_cli-0.0.6.dist-info}/entry_points.txt +0 -0
- {vuer_cli-0.0.4.dist-info → vuer_cli-0.0.6.dist-info}/licenses/LICENSE +0 -0
vuer_cli/scripts/minimap.py
CHANGED
|
@@ -1,41 +1,22 @@
|
|
|
1
|
-
"""Minimap visualization with GLB model
|
|
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
|
-
|
|
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
|
|
214
|
-
import
|
|
140
|
+
"""Main visualization with frame-by-frame depth point cloud and robot model playback."""
|
|
141
|
+
import math
|
|
215
142
|
|
|
216
|
-
from vuer.schemas import
|
|
143
|
+
from vuer.schemas import Glb
|
|
217
144
|
|
|
218
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
227
|
-
|
|
152
|
+
# Construct proper GLB URL using workspace prefix
|
|
153
|
+
glb_full_url = str(app.localhost_prefix / glb_url)
|
|
228
154
|
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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
|
-
|
|
164
|
+
print(f"Starting playback: {len(depth_frames)} frames at {fps} FPS (loop={loop})")
|
|
239
165
|
|
|
240
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
285
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
307
|
-
|
|
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
|
-
-
|
|
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 = "
|
|
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
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
|
281
|
+
def __call__(self) -> int:
|
|
366
282
|
"""Execute minimap command."""
|
|
367
283
|
try:
|
|
368
|
-
from vuer import Vuer
|
|
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
|
-
#
|
|
405
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
|
vuer_cli/scripts/viz_ptc_cams.py
CHANGED
|
@@ -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
|
|
555
|
+
def __call__(self) -> int:
|
|
556
556
|
"""Execute viz_ptc_cams command."""
|
|
557
557
|
try:
|
|
558
558
|
import cv2
|