vuer-cli 0.0.3__py3-none-any.whl → 0.0.5__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 +69 -84
- vuer_cli/envs_publish.py +335 -309
- vuer_cli/envs_pull.py +177 -170
- vuer_cli/login.py +459 -0
- vuer_cli/main.py +52 -88
- vuer_cli/mcap_extractor.py +866 -0
- vuer_cli/remove.py +87 -95
- vuer_cli/scripts/demcap.py +171 -0
- vuer_cli/scripts/mcap_playback.py +661 -0
- vuer_cli/scripts/minimap.py +365 -0
- vuer_cli/scripts/ptc_utils.py +434 -0
- vuer_cli/scripts/viz_ptc_cams.py +613 -0
- vuer_cli/scripts/viz_ptc_proxie.py +483 -0
- vuer_cli/sync.py +314 -308
- vuer_cli/upgrade.py +121 -136
- vuer_cli/utils.py +11 -38
- {vuer_cli-0.0.3.dist-info → vuer_cli-0.0.5.dist-info}/METADATA +59 -6
- vuer_cli-0.0.5.dist-info/RECORD +22 -0
- vuer_cli-0.0.3.dist-info/RECORD +0 -14
- {vuer_cli-0.0.3.dist-info → vuer_cli-0.0.5.dist-info}/WHEEL +0 -0
- {vuer_cli-0.0.3.dist-info → vuer_cli-0.0.5.dist-info}/entry_points.txt +0 -0
- {vuer_cli-0.0.3.dist-info → vuer_cli-0.0.5.dist-info}/licenses/LICENSE +0 -0
vuer_cli/remove.py
CHANGED
|
@@ -1,105 +1,97 @@
|
|
|
1
1
|
"""Remove command - remove an environment spec from environment.json then sync."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import json
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
# typing imports not required
|
|
6
5
|
|
|
7
|
-
import
|
|
6
|
+
from params_proto import proto
|
|
8
7
|
|
|
9
8
|
from .sync import Sync, read_environments_lock
|
|
10
|
-
from .utils import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# Use shared parser from utils; legacy '@' syntax is not supported.
|
|
9
|
+
from .utils import normalize_env_spec, parse_env_spec, print_error
|
|
14
10
|
|
|
15
11
|
|
|
16
|
-
@
|
|
12
|
+
@proto
|
|
17
13
|
class Remove:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
"""Remove an environment from environment.json and run `vuer sync`.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
vuer remove some-environment/v1.2.3
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Required positional arg: environment spec, e.g. "some-environment/v1.2.3"
|
|
21
|
+
env: str
|
|
22
|
+
|
|
23
|
+
def __call__(self) -> int:
|
|
24
|
+
"""Execute remove command."""
|
|
25
|
+
try:
|
|
26
|
+
env_spec = self.env
|
|
27
|
+
|
|
28
|
+
name, version = parse_env_spec(env_spec)
|
|
29
|
+
env_spec_normalized = normalize_env_spec(f"{name}/{version}")
|
|
30
|
+
|
|
31
|
+
cwd = Path.cwd()
|
|
32
|
+
module_dir = cwd / "vuer_environments"
|
|
33
|
+
lock_path = cwd / "environments-lock.yaml"
|
|
34
|
+
|
|
35
|
+
# Step 2: Ensure vuer_environments/dependencies.toml exists
|
|
36
|
+
if not module_dir.exists() or not lock_path.exists():
|
|
37
|
+
raise FileNotFoundError(
|
|
38
|
+
"vuer_environments directory or environments-lock.yaml not found. "
|
|
39
|
+
"Please run `vuer sync` first to generate environments-lock.yaml."
|
|
40
|
+
)
|
|
41
|
+
existing_deps = read_environments_lock(lock_path)
|
|
42
|
+
if env_spec_normalized not in existing_deps:
|
|
43
|
+
print(f"[INFO] Environment {env_spec_normalized} is not present in {lock_path}")
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
# Step 3: Remove from environment.json dependencies, then run sync
|
|
47
|
+
env_json_path = cwd / "environment.json"
|
|
48
|
+
if not env_json_path.exists():
|
|
49
|
+
raise FileNotFoundError("environment.json not found. Cannot remove dependency.")
|
|
50
|
+
|
|
51
|
+
with env_json_path.open("r", encoding="utf-8") as f:
|
|
29
52
|
try:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
# Remove the dependency if present and version matches exactly.
|
|
78
|
-
current_version = deps.get(name)
|
|
79
|
-
if current_version is None:
|
|
80
|
-
print(f"[INFO] Dependency {env_spec_normalized} not found in environment.json. Skipping removal.")
|
|
81
|
-
else:
|
|
82
|
-
# Only remove if the version in environment.json matches the requested version.
|
|
83
|
-
if current_version != version:
|
|
84
|
-
print(
|
|
85
|
-
f"[INFO] Skipping removal: environment '{name}' is pinned to version "
|
|
86
|
-
f"'{current_version}' in environment.json (requested '{version}')."
|
|
87
|
-
)
|
|
88
|
-
else:
|
|
89
|
-
deps.pop(name, None)
|
|
90
|
-
print(f"[INFO] Removed {env_spec_normalized} from environment.json dependencies.")
|
|
91
|
-
|
|
92
|
-
data["dependencies"] = deps
|
|
93
|
-
with env_json_path.open("w", encoding="utf-8") as f:
|
|
94
|
-
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
95
|
-
f.write("\n")
|
|
96
|
-
|
|
97
|
-
print("[INFO] Running sync to reconcile vuer_environments/ with updated dependencies...")
|
|
98
|
-
return Sync()()
|
|
99
|
-
|
|
100
|
-
except (FileNotFoundError, ValueError, RuntimeError) as e:
|
|
101
|
-
print_error(str(e))
|
|
102
|
-
return 1
|
|
103
|
-
except Exception as e:
|
|
104
|
-
print_error(f"Unexpected error: {e}")
|
|
105
|
-
return 1
|
|
53
|
+
data = json.load(f)
|
|
54
|
+
except json.JSONDecodeError as e:
|
|
55
|
+
raise ValueError(f"Invalid environment.json: {e}") from e
|
|
56
|
+
|
|
57
|
+
deps = data.get("dependencies")
|
|
58
|
+
if deps is None:
|
|
59
|
+
deps = {}
|
|
60
|
+
if not isinstance(deps, dict):
|
|
61
|
+
raise ValueError("environment.json 'dependencies' field must be an object")
|
|
62
|
+
|
|
63
|
+
# Remove the dependency if present and version matches exactly.
|
|
64
|
+
current_version = deps.get(name)
|
|
65
|
+
if current_version is None:
|
|
66
|
+
print(
|
|
67
|
+
f"[INFO] Dependency {env_spec_normalized} not found in environment.json. Skipping removal."
|
|
68
|
+
)
|
|
69
|
+
else:
|
|
70
|
+
# Only remove if the version in environment.json matches the requested version.
|
|
71
|
+
if current_version != version:
|
|
72
|
+
print(
|
|
73
|
+
f"[INFO] Skipping removal: environment '{name}' is pinned to version "
|
|
74
|
+
f"'{current_version}' in environment.json (requested '{version}')."
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
deps.pop(name, None)
|
|
78
|
+
print(
|
|
79
|
+
f"[INFO] Removed {env_spec_normalized} from environment.json dependencies."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
data["dependencies"] = deps
|
|
83
|
+
with env_json_path.open("w", encoding="utf-8") as f:
|
|
84
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
85
|
+
f.write("\n")
|
|
86
|
+
|
|
87
|
+
print(
|
|
88
|
+
"[INFO] Running sync to reconcile vuer_environments/ with updated dependencies..."
|
|
89
|
+
)
|
|
90
|
+
return Sync().run()
|
|
91
|
+
|
|
92
|
+
except (FileNotFoundError, ValueError, RuntimeError) as e:
|
|
93
|
+
print_error(str(e))
|
|
94
|
+
return 1
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print_error(f"Unexpected error: {e}")
|
|
97
|
+
return 1
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Demcap command - extract data from MCAP files to readable formats."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from textwrap import dedent
|
|
6
|
+
|
|
7
|
+
from ..utils import print_error
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class Demcap:
|
|
12
|
+
"""Extract data from MCAP files to readable formats.
|
|
13
|
+
|
|
14
|
+
DESCRIPTION
|
|
15
|
+
Extracts and organizes data from ROS2 MCAP bag files into human-readable
|
|
16
|
+
formats. Supports RGB images, depth images, LiDAR point clouds, IMU data,
|
|
17
|
+
camera intrinsics, TF transforms, odometry, and joint states.
|
|
18
|
+
|
|
19
|
+
Output structure:
|
|
20
|
+
<output_dir>/
|
|
21
|
+
├── images/ # RGB images per camera
|
|
22
|
+
├── depth/ # Depth images per camera
|
|
23
|
+
├── lidar/ # LiDAR point cloud data
|
|
24
|
+
├── imu/ # IMU sensor data (CSV)
|
|
25
|
+
├── transforms/ # TF transform data (CSV)
|
|
26
|
+
├── videos/ # Generated videos from images
|
|
27
|
+
├── camera_intrinsics.json
|
|
28
|
+
├── odometry.csv
|
|
29
|
+
└── joint_states.csv
|
|
30
|
+
|
|
31
|
+
EXAMPLES
|
|
32
|
+
# Basic extraction (output to extracted_data/<mcap_name>/)
|
|
33
|
+
vuer demcap /path/to/recording.mcap
|
|
34
|
+
|
|
35
|
+
# Specify custom output directory
|
|
36
|
+
vuer demcap /path/to/recording.mcap --output ./my_output
|
|
37
|
+
|
|
38
|
+
# Skip video generation for faster extraction
|
|
39
|
+
vuer demcap /path/to/recording.mcap --no-video
|
|
40
|
+
|
|
41
|
+
# Extract specific time range (nanoseconds)
|
|
42
|
+
vuer demcap /path/to/recording.mcap --start-time 1000000000 --end-time 2000000000
|
|
43
|
+
|
|
44
|
+
DEPENDENCIES
|
|
45
|
+
Requires: pip install 'vuer-cli[mcap]'
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
# Required: path to mcap file
|
|
49
|
+
mcap_path: str
|
|
50
|
+
|
|
51
|
+
# Optional arguments
|
|
52
|
+
output: str = None # Output directory (default: extracted_data/<mcap_filename>)
|
|
53
|
+
no_video: bool = False # Skip video creation from RGB images
|
|
54
|
+
fps: int = 30 # Frame rate for output videos
|
|
55
|
+
start_time: int = None # Start timestamp in nanoseconds
|
|
56
|
+
end_time: int = None # End timestamp in nanoseconds
|
|
57
|
+
|
|
58
|
+
def __call__(self) -> int:
|
|
59
|
+
"""Execute demcap command."""
|
|
60
|
+
try:
|
|
61
|
+
from .mcap_extractor import MCAPExtractor
|
|
62
|
+
except ImportError as e:
|
|
63
|
+
print_error(
|
|
64
|
+
f"Missing dependencies for demcap command: {e}\n\n"
|
|
65
|
+
"Install with:\n"
|
|
66
|
+
" pip install 'vuer-cli[mcap]'\n"
|
|
67
|
+
" # or: uv add 'vuer-cli[mcap]'"
|
|
68
|
+
)
|
|
69
|
+
return 1
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
# Verify file exists
|
|
73
|
+
mcap_path = Path(self.mcap_path)
|
|
74
|
+
if not mcap_path.exists():
|
|
75
|
+
raise FileNotFoundError(f"File {self.mcap_path} does not exist")
|
|
76
|
+
|
|
77
|
+
# Determine output directory
|
|
78
|
+
if self.output:
|
|
79
|
+
output_dir = Path(self.output).absolute()
|
|
80
|
+
else:
|
|
81
|
+
mcap_name = mcap_path.stem
|
|
82
|
+
output_dir = Path(f"extracted_data/{mcap_name}").absolute()
|
|
83
|
+
|
|
84
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
|
|
86
|
+
print(f"\nExtracting MCAP file: {self.mcap_path}")
|
|
87
|
+
print(f"Output directory: {output_dir}")
|
|
88
|
+
print(f"Create videos: {not self.no_video}")
|
|
89
|
+
if self.start_time and self.end_time:
|
|
90
|
+
print(f"Time range: {self.start_time} to {self.end_time}")
|
|
91
|
+
print()
|
|
92
|
+
|
|
93
|
+
# Create extractor instance
|
|
94
|
+
extractor = MCAPExtractor(
|
|
95
|
+
mcap_path=str(mcap_path),
|
|
96
|
+
output_dir=str(output_dir),
|
|
97
|
+
create_videos=not self.no_video,
|
|
98
|
+
video_fps=self.fps,
|
|
99
|
+
start_time_ns=self.start_time,
|
|
100
|
+
end_time_ns=self.end_time,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Extract all data
|
|
104
|
+
extractor.extract_all()
|
|
105
|
+
|
|
106
|
+
# Show extraction summary
|
|
107
|
+
self._print_summary(extractor, output_dir)
|
|
108
|
+
|
|
109
|
+
return 0
|
|
110
|
+
|
|
111
|
+
except FileNotFoundError as e:
|
|
112
|
+
print_error(str(e))
|
|
113
|
+
return 1
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print_error(f"Unexpected error: {e}")
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
def _print_summary(self, extractor, output_dir: Path):
|
|
119
|
+
"""Print summary of extracted data."""
|
|
120
|
+
print(
|
|
121
|
+
dedent(f"""
|
|
122
|
+
{"=" * 80}
|
|
123
|
+
EXTRACTION COMPLETE
|
|
124
|
+
{"=" * 80}
|
|
125
|
+
|
|
126
|
+
Extracted data locations:
|
|
127
|
+
RGB images: {output_dir / "images"}
|
|
128
|
+
Depth images: {output_dir / "depth"}
|
|
129
|
+
LiDAR scans: {output_dir / "lidar"}
|
|
130
|
+
IMU data: {output_dir / "imu"}
|
|
131
|
+
Transforms: {output_dir / "transforms"}
|
|
132
|
+
Videos: {output_dir / "videos"}
|
|
133
|
+
""").strip()
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Show camera intrinsics if available
|
|
137
|
+
if extractor.camera_intrinsics:
|
|
138
|
+
print("\nCamera intrinsics extracted:")
|
|
139
|
+
for camera_name, info in extractor.camera_intrinsics.items():
|
|
140
|
+
print(f" {camera_name}: {info['width']}x{info['height']}")
|
|
141
|
+
if info["K"] and len(info["K"]) == 9:
|
|
142
|
+
fx, fy = info["K"][0], info["K"][4]
|
|
143
|
+
cx, cy = info["K"][2], info["K"][5]
|
|
144
|
+
print(f" fx={fx:.2f}, fy={fy:.2f}, cx={cx:.2f}, cy={cy:.2f}")
|
|
145
|
+
|
|
146
|
+
# Show IMU data stats
|
|
147
|
+
if extractor.imu_data:
|
|
148
|
+
print("\nIMU data extracted:")
|
|
149
|
+
for topic_name, data_list in extractor.imu_data.items():
|
|
150
|
+
print(f" {topic_name}: {len(data_list)} records")
|
|
151
|
+
|
|
152
|
+
# Show transform data stats
|
|
153
|
+
if extractor.tf_data:
|
|
154
|
+
print(f"\nTransform data: {len(extractor.tf_data)} records")
|
|
155
|
+
|
|
156
|
+
# Show odometry data stats
|
|
157
|
+
if extractor.odom_data:
|
|
158
|
+
print(f"\nOdometry data: {len(extractor.odom_data)} records")
|
|
159
|
+
|
|
160
|
+
# Show joint states data stats
|
|
161
|
+
if extractor.joint_states_data:
|
|
162
|
+
print(f"\nJoint states data: {len(extractor.joint_states_data)} records")
|
|
163
|
+
|
|
164
|
+
print(
|
|
165
|
+
dedent(f"""
|
|
166
|
+
{"=" * 80}
|
|
167
|
+
|
|
168
|
+
All data saved to: {output_dir}
|
|
169
|
+
{"=" * 80}
|
|
170
|
+
""").strip()
|
|
171
|
+
)
|