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/remove.py CHANGED
@@ -1,105 +1,97 @@
1
1
  """Remove command - remove an environment spec from environment.json then sync."""
2
2
 
3
- from dataclasses import dataclass
3
+ import json
4
4
  from pathlib import Path
5
- # typing imports not required
6
5
 
7
- import json
6
+ from params_proto import proto
8
7
 
9
8
  from .sync import Sync, read_environments_lock
10
- from .utils import print_error, parse_env_spec, normalize_env_spec
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
- @dataclass
12
+ @proto
17
13
  class Remove:
18
- """Remove an environment from environment.json and run `vuer sync`.
19
-
20
- Example:
21
- vuer remove some-environment/v1.2.3
22
- """
23
-
24
- # Primary usage is positional: `vuer remove name/version`.
25
- env: str = "" # Environment spec to remove, e.g. "some-environment/v1.2.3"
26
-
27
- def __call__(self) -> int:
28
- """Execute remove command."""
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
- env_spec = self.env
31
- if not env_spec:
32
- raise ValueError(
33
- "Missing environment spec. Usage: vuer remove some-environment/v1.2.3"
34
- )
35
-
36
- name, version = parse_env_spec(env_spec)
37
- env_spec_normalized = normalize_env_spec(f"{name}/{version}")
38
-
39
- cwd = Path.cwd()
40
- module_dir = cwd / "vuer_environments"
41
- lock_path = cwd / "environments-lock.yaml"
42
-
43
- # Step 2: Ensure vuer_environments/dependencies.toml exists
44
- if not module_dir.exists() or not lock_path.exists():
45
- raise FileNotFoundError(
46
- "vuer_environments directory or environments-lock.yaml not found. "
47
- "Please run `vuer sync` first to generate environments-lock.yaml."
48
- )
49
- existing_deps = read_environments_lock(lock_path)
50
- if env_spec_normalized not in existing_deps:
51
- print(f"[INFO] Environment {env_spec_normalized} is not present in {lock_path}")
52
- return 0
53
-
54
- # Step 3: Remove from environment.json dependencies, then run sync
55
- env_json_path = cwd / "environment.json"
56
- if not env_json_path.exists():
57
- raise FileNotFoundError(
58
- "environment.json not found. Cannot remove dependency."
59
- )
60
-
61
- with env_json_path.open("r", encoding="utf-8") as f:
62
- try:
63
- data = json.load(f)
64
- except json.JSONDecodeError as e:
65
- raise ValueError(
66
- f"Invalid environment.json: {e}"
67
- ) from e
68
-
69
- deps = data.get("dependencies")
70
- if deps is None:
71
- deps = {}
72
- if not isinstance(deps, dict):
73
- raise ValueError(
74
- "environment.json 'dependencies' field must be an object"
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
+ )