dexcontrol 0.2.4__py3-none-any.whl → 0.2.10__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.
Potentially problematic release.
This version of dexcontrol might be problematic. Click here for more details.
- dexcontrol/config/core/arm.py +2 -0
- dexcontrol/config/core/hand.py +5 -16
- dexcontrol/config/sensors/cameras/__init__.py +2 -0
- dexcontrol/config/sensors/cameras/rgb_camera.py +24 -7
- dexcontrol/config/sensors/cameras/zed_camera.py +34 -15
- dexcontrol/config/sensors/vega_sensors.py +1 -0
- dexcontrol/config/vega.py +6 -0
- dexcontrol/core/arm.py +33 -6
- dexcontrol/core/component.py +51 -2
- dexcontrol/core/hand.py +81 -13
- dexcontrol/core/head.py +20 -3
- dexcontrol/core/torso.py +20 -5
- dexcontrol/proto/dexcontrol_query_pb2.py +15 -15
- dexcontrol/proto/dexcontrol_query_pb2.pyi +6 -2
- dexcontrol/robot.py +112 -37
- dexcontrol/sensors/camera/rgb_camera.py +98 -29
- dexcontrol/sensors/camera/zed_camera.py +214 -189
- dexcontrol/sensors/lidar/rplidar.py +13 -30
- dexcontrol/utils/pb_utils.py +6 -1
- dexcontrol/utils/subscribers/camera.py +1 -1
- dexcontrol/utils/subscribers/lidar.py +16 -45
- dexcontrol/utils/subscribers/rtc.py +2 -1
- dexcontrol/utils/viz_utils.py +4 -4
- {dexcontrol-0.2.4.dist-info → dexcontrol-0.2.10.dist-info}/METADATA +42 -17
- {dexcontrol-0.2.4.dist-info → dexcontrol-0.2.10.dist-info}/RECORD +27 -27
- {dexcontrol-0.2.4.dist-info → dexcontrol-0.2.10.dist-info}/WHEEL +0 -0
- {dexcontrol-0.2.4.dist-info → dexcontrol-0.2.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,13 +11,19 @@
|
|
|
11
11
|
"""ZED camera sensor implementation using RTC subscribers for RGB and Zenoh subscriber for depth."""
|
|
12
12
|
|
|
13
13
|
import logging
|
|
14
|
+
import time
|
|
15
|
+
from typing import Any, Dict, Optional, Union
|
|
14
16
|
|
|
15
17
|
import numpy as np
|
|
16
18
|
import zenoh
|
|
17
19
|
|
|
20
|
+
from dexcontrol.config.sensors.cameras import ZedCameraConfig
|
|
18
21
|
from dexcontrol.utils.os_utils import resolve_key_name
|
|
19
22
|
from dexcontrol.utils.rtc_utils import create_rtc_subscriber_from_zenoh
|
|
20
|
-
from dexcontrol.utils.subscribers.camera import
|
|
23
|
+
from dexcontrol.utils.subscribers.camera import (
|
|
24
|
+
DepthCameraSubscriber,
|
|
25
|
+
RGBCameraSubscriber,
|
|
26
|
+
)
|
|
21
27
|
from dexcontrol.utils.subscribers.rtc import RTCSubscriber
|
|
22
28
|
from dexcontrol.utils.zenoh_utils import query_zenoh_json
|
|
23
29
|
|
|
@@ -34,289 +40,277 @@ except ImportError:
|
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
class ZedCameraSensor:
|
|
37
|
-
"""ZED camera sensor
|
|
43
|
+
"""ZED camera sensor for multi-stream (RGB, Depth) data acquisition.
|
|
38
44
|
|
|
39
|
-
This sensor
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Note: For depth data decoding, dexsensor package is required.
|
|
45
|
+
This sensor manages left RGB, right RGB, and depth data streams from a ZED
|
|
46
|
+
camera. It can be configured to use high-performance RTC subscribers for RGB
|
|
47
|
+
streams (`use_rtc=True`) or fall back to standard Zenoh subscribers
|
|
48
|
+
(`use_rtc=False`). The depth stream always uses a standard Zenoh subscriber.
|
|
44
49
|
"""
|
|
45
50
|
|
|
51
|
+
SubscriberType = Union[RTCSubscriber, DepthCameraSubscriber, RGBCameraSubscriber]
|
|
52
|
+
|
|
46
53
|
def __init__(
|
|
47
54
|
self,
|
|
48
|
-
configs,
|
|
55
|
+
configs: ZedCameraConfig,
|
|
49
56
|
zenoh_session: zenoh.Session,
|
|
50
|
-
*args,
|
|
51
|
-
**kwargs,
|
|
52
57
|
) -> None:
|
|
53
|
-
"""Initialize the ZED camera sensor.
|
|
58
|
+
"""Initialize the ZED camera sensor and its subscribers.
|
|
54
59
|
|
|
55
60
|
Args:
|
|
56
|
-
configs: Configuration for the ZED camera
|
|
61
|
+
configs: Configuration object for the ZED camera.
|
|
57
62
|
zenoh_session: Active Zenoh session for communication.
|
|
58
63
|
"""
|
|
59
64
|
self._name = configs.name
|
|
60
65
|
self._zenoh_session = zenoh_session
|
|
61
66
|
self._configs = configs
|
|
67
|
+
self._subscribers: Dict[str, Optional[ZedCameraSensor.SubscriberType]] = {}
|
|
68
|
+
self._camera_info: Optional[Dict[str, Any]] = None
|
|
62
69
|
|
|
63
|
-
# Initialize subscribers dictionary - RGB uses RTC, depth uses Zenoh
|
|
64
|
-
self._subscribers: dict[str, RTCSubscriber | DepthCameraSubscriber | None] = {}
|
|
65
|
-
|
|
66
|
-
# Create subscribers for each enabled stream
|
|
67
70
|
self._create_subscribers()
|
|
71
|
+
self._query_camera_info()
|
|
72
|
+
|
|
73
|
+
def _create_subscriber(
|
|
74
|
+
self, stream_name: str, stream_config: Dict[str, Any]
|
|
75
|
+
) -> Optional[SubscriberType]:
|
|
76
|
+
"""Factory method to create a subscriber based on stream type and config."""
|
|
77
|
+
try:
|
|
78
|
+
if not stream_config.get("enable", False):
|
|
79
|
+
logger.info(f"'{self._name}': Stream '{stream_name}' is disabled.")
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
# Create Depth subscriber
|
|
83
|
+
if stream_name == "depth":
|
|
84
|
+
topic = stream_config.get("topic")
|
|
85
|
+
if not topic:
|
|
86
|
+
logger.warning(f"'{self._name}': No 'topic' for depth stream.")
|
|
87
|
+
return None
|
|
88
|
+
logger.info(f"'{self._name}': Creating Zenoh depth subscriber.")
|
|
89
|
+
return DepthCameraSubscriber(
|
|
90
|
+
topic=topic,
|
|
91
|
+
zenoh_session=self._zenoh_session,
|
|
92
|
+
name=f"{self._name}_{stream_name}_subscriber",
|
|
93
|
+
enable_fps_tracking=self._configs.enable_fps_tracking,
|
|
94
|
+
fps_log_interval=self._configs.fps_log_interval,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Create RGB subscriber (RTC or Zenoh)
|
|
98
|
+
if self._configs.use_rtc:
|
|
99
|
+
info_key = stream_config.get("info_key")
|
|
100
|
+
if not info_key:
|
|
101
|
+
logger.warning(f"'{self._name}': No 'info_key' for RTC stream '{stream_name}'.")
|
|
102
|
+
return None
|
|
103
|
+
logger.info(f"'{self._name}': Creating RTC subscriber for '{stream_name}'.")
|
|
104
|
+
return create_rtc_subscriber_from_zenoh(
|
|
105
|
+
zenoh_session=self._zenoh_session,
|
|
106
|
+
info_topic=info_key,
|
|
107
|
+
name=f"{self._name}_{stream_name}_subscriber",
|
|
108
|
+
enable_fps_tracking=self._configs.enable_fps_tracking,
|
|
109
|
+
fps_log_interval=self._configs.fps_log_interval,
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
topic = stream_config.get("topic")
|
|
113
|
+
if not topic:
|
|
114
|
+
logger.warning(f"'{self._name}': No 'topic' for Zenoh stream '{stream_name}'.")
|
|
115
|
+
return None
|
|
116
|
+
logger.info(f"'{self._name}': Creating Zenoh RGB subscriber for '{stream_name}'.")
|
|
117
|
+
return RGBCameraSubscriber(
|
|
118
|
+
topic=topic,
|
|
119
|
+
zenoh_session=self._zenoh_session,
|
|
120
|
+
name=f"{self._name}_{stream_name}_subscriber",
|
|
121
|
+
enable_fps_tracking=self._configs.enable_fps_tracking,
|
|
122
|
+
fps_log_interval=self._configs.fps_log_interval,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.error(f"Error creating subscriber for '{self._name}/{stream_name}': {e}")
|
|
127
|
+
return None
|
|
68
128
|
|
|
69
129
|
def _create_subscribers(self) -> None:
|
|
70
|
-
"""Create subscribers for
|
|
130
|
+
"""Create subscribers for all configured camera streams."""
|
|
71
131
|
subscriber_config = self._configs.subscriber_config
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
'right_rgb': subscriber_config.get('right_rgb', {}),
|
|
77
|
-
'depth': subscriber_config.get('depth', {})
|
|
132
|
+
stream_definitions = {
|
|
133
|
+
"left_rgb": subscriber_config.get("left_rgb", {}),
|
|
134
|
+
"right_rgb": subscriber_config.get("right_rgb", {}),
|
|
135
|
+
"depth": subscriber_config.get("depth", {}),
|
|
78
136
|
}
|
|
79
137
|
|
|
80
|
-
for
|
|
81
|
-
|
|
82
|
-
try:
|
|
83
|
-
if stream_name == 'depth':
|
|
84
|
-
# Use regular Zenoh subscriber for depth
|
|
85
|
-
topic = stream_config.get('topic')
|
|
86
|
-
if topic:
|
|
87
|
-
subscriber = DepthCameraSubscriber(
|
|
88
|
-
topic=topic,
|
|
89
|
-
zenoh_session=self._zenoh_session,
|
|
90
|
-
name=f"{self._name}_{stream_name}_subscriber",
|
|
91
|
-
enable_fps_tracking=self._configs.enable_fps_tracking,
|
|
92
|
-
fps_log_interval=self._configs.fps_log_interval,
|
|
93
|
-
)
|
|
94
|
-
logger.info(f"Created Zenoh depth subscriber for {self._name} {stream_name}")
|
|
95
|
-
self._subscribers[stream_name] = subscriber
|
|
96
|
-
else:
|
|
97
|
-
logger.warning(f"No topic found for {self._name} {stream_name}")
|
|
98
|
-
self._subscribers[stream_name] = None
|
|
99
|
-
else:
|
|
100
|
-
# Use RTC subscriber for RGB streams
|
|
101
|
-
info_key = stream_config.get('info_key')
|
|
102
|
-
if info_key:
|
|
103
|
-
subscriber = create_rtc_subscriber_from_zenoh(
|
|
104
|
-
zenoh_session=self._zenoh_session,
|
|
105
|
-
info_topic=info_key,
|
|
106
|
-
name=f"{self._name}_{stream_name}_subscriber",
|
|
107
|
-
enable_fps_tracking=self._configs.enable_fps_tracking,
|
|
108
|
-
fps_log_interval=self._configs.fps_log_interval,
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
if subscriber is None:
|
|
112
|
-
logger.warning(f"Failed to create RTC subscriber for {self._name} {stream_name}")
|
|
113
|
-
else:
|
|
114
|
-
logger.info(f"Created RTC subscriber for {self._name} {stream_name}")
|
|
115
|
-
|
|
116
|
-
self._subscribers[stream_name] = subscriber
|
|
117
|
-
else:
|
|
118
|
-
logger.warning(f"No info_key found for {self._name} {stream_name}")
|
|
119
|
-
self._subscribers[stream_name] = None
|
|
120
|
-
except Exception as e:
|
|
121
|
-
logger.error(f"Error creating subscriber for {self._name} {stream_name}: {e}")
|
|
122
|
-
self._subscribers[stream_name] = None
|
|
123
|
-
else:
|
|
124
|
-
logger.info(f"Stream {stream_name} disabled for {self._name}")
|
|
125
|
-
self._subscribers[stream_name] = None
|
|
126
|
-
|
|
127
|
-
# Query for camera info - use info_key from one of the RGB streams
|
|
128
|
-
enabled_rgb_configs = [config for config in [subscriber_config.get('left_rgb'), subscriber_config.get('right_rgb')] if config and config.get('enable')]
|
|
129
|
-
if enabled_rgb_configs:
|
|
130
|
-
info_key = resolve_key_name(enabled_rgb_configs[0].get('info_key')).rstrip('/')
|
|
131
|
-
info_key_root = '/'.join(info_key.split('/')[:-2])
|
|
132
|
-
info_key = f"{info_key_root}/info"
|
|
133
|
-
info = query_zenoh_json(self._zenoh_session, info_key)
|
|
134
|
-
self._camera_info = info
|
|
135
|
-
if info is not None:
|
|
136
|
-
self._depth_min = info.get('depth_min')
|
|
137
|
-
self._depth_max = info.get('depth_max')
|
|
138
|
-
else:
|
|
139
|
-
logger.warning(f"No camera info found for {self._name}")
|
|
140
|
-
self._depth_min = None
|
|
141
|
-
self._depth_max = None
|
|
142
|
-
else:
|
|
143
|
-
logger.warning(f"No enabled RGB streams found for camera info query for {self._name}")
|
|
144
|
-
self._camera_info = None
|
|
145
|
-
self._depth_min = None
|
|
146
|
-
self._depth_max = None
|
|
138
|
+
for name, config in stream_definitions.items():
|
|
139
|
+
self._subscribers[name] = self._create_subscriber(name, config)
|
|
147
140
|
|
|
148
|
-
def
|
|
149
|
-
"""
|
|
141
|
+
def _query_camera_info(self) -> None:
|
|
142
|
+
"""Query Zenoh for camera metadata if using RTC."""
|
|
143
|
+
if not self._configs.use_rtc:
|
|
144
|
+
logger.info(f"'{self._name}': Skipping camera info query in non-RTC mode.")
|
|
145
|
+
return
|
|
150
146
|
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
enabled_rgb_streams = [
|
|
148
|
+
s
|
|
149
|
+
for s_name, s in self._subscribers.items()
|
|
150
|
+
if "rgb" in s_name and s is not None
|
|
151
|
+
]
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
if not enabled_rgb_streams:
|
|
154
|
+
logger.warning(f"'{self._name}': No enabled RGB streams to query for camera info.")
|
|
155
|
+
return
|
|
156
156
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return None
|
|
157
|
+
# Use the info_key from the first available RGB subscriber's config
|
|
158
|
+
first_stream_name = "left_rgb" if self._subscribers.get("left_rgb") else "right_rgb"
|
|
159
|
+
stream_config = self._configs.subscriber_config.get(first_stream_name, {})
|
|
160
|
+
info_key = stream_config.get("info_key")
|
|
162
161
|
|
|
163
|
-
if not
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
"Please install dexsensor: pip install dexsensor"
|
|
167
|
-
)
|
|
162
|
+
if not info_key:
|
|
163
|
+
logger.warning(f"'{self._name}': Could not find info_key for camera info query.")
|
|
164
|
+
return
|
|
168
165
|
|
|
169
166
|
try:
|
|
170
|
-
#
|
|
171
|
-
|
|
172
|
-
|
|
167
|
+
# Construct the root info key (e.g., 'camera/head/info')
|
|
168
|
+
resolved_key = resolve_key_name(info_key).rstrip("/")
|
|
169
|
+
info_key_root = "/".join(resolved_key.split("/")[:-2])
|
|
170
|
+
final_info_key = f"{info_key_root}/info"
|
|
171
|
+
|
|
172
|
+
logger.info(f"'{self._name}': Querying for camera info at '{final_info_key}'.")
|
|
173
|
+
self._camera_info = query_zenoh_json(self._zenoh_session, final_info_key)
|
|
174
|
+
|
|
175
|
+
if self._camera_info:
|
|
176
|
+
logger.info(f"'{self._name}': Successfully received camera info.")
|
|
177
|
+
else:
|
|
178
|
+
logger.warning(f"'{self._name}': No camera info found at '{final_info_key}'.")
|
|
173
179
|
except Exception as e:
|
|
174
|
-
|
|
180
|
+
logger.error(f"'{self._name}': Failed to query camera info: {e}")
|
|
175
181
|
|
|
176
182
|
def shutdown(self) -> None:
|
|
177
|
-
"""Shutdown the camera sensor."""
|
|
183
|
+
"""Shutdown all active subscribers for the camera sensor."""
|
|
184
|
+
logger.info(f"Shutting down all subscribers for '{self._name}'.")
|
|
178
185
|
for stream_name, subscriber in self._subscribers.items():
|
|
179
186
|
if subscriber:
|
|
180
187
|
try:
|
|
181
188
|
subscriber.shutdown()
|
|
182
|
-
logger.
|
|
189
|
+
logger.debug(f"'{self._name}': Subscriber '{stream_name}' shut down.")
|
|
183
190
|
except Exception as e:
|
|
184
|
-
logger.error(
|
|
191
|
+
logger.error(
|
|
192
|
+
f"Error shutting down '{stream_name}' subscriber for '{self._name}': {e}"
|
|
193
|
+
)
|
|
194
|
+
logger.info(f"'{self._name}' sensor shut down.")
|
|
185
195
|
|
|
186
196
|
def is_active(self) -> bool:
|
|
187
|
-
"""Check if any camera
|
|
197
|
+
"""Check if any of the camera's subscribers are actively receiving data.
|
|
188
198
|
|
|
189
199
|
Returns:
|
|
190
|
-
True if at least one
|
|
200
|
+
True if at least one subscriber is active, False otherwise.
|
|
191
201
|
"""
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return False
|
|
202
|
+
return any(
|
|
203
|
+
sub.is_active() for sub in self._subscribers.values() if sub is not None
|
|
204
|
+
)
|
|
196
205
|
|
|
197
206
|
def is_stream_active(self, stream_name: str) -> bool:
|
|
198
|
-
"""Check if a specific stream is actively receiving data.
|
|
207
|
+
"""Check if a specific camera stream is actively receiving data.
|
|
199
208
|
|
|
200
209
|
Args:
|
|
201
|
-
stream_name:
|
|
210
|
+
stream_name: The name of the stream (e.g., 'left_rgb', 'depth').
|
|
202
211
|
|
|
203
212
|
Returns:
|
|
204
|
-
True if the stream is
|
|
213
|
+
True if the specified stream's subscriber is active, False otherwise.
|
|
205
214
|
"""
|
|
206
215
|
subscriber = self._subscribers.get(stream_name)
|
|
207
216
|
return subscriber.is_active() if subscriber else False
|
|
208
217
|
|
|
209
218
|
def wait_for_active(self, timeout: float = 5.0, require_all: bool = False) -> bool:
|
|
210
|
-
"""Wait for camera streams to
|
|
219
|
+
"""Wait for camera streams to become active.
|
|
211
220
|
|
|
212
221
|
Args:
|
|
213
|
-
timeout: Maximum time to wait in seconds.
|
|
214
|
-
require_all: If True,
|
|
222
|
+
timeout: Maximum time to wait in seconds for each subscriber.
|
|
223
|
+
require_all: If True, waits for all enabled streams to become active.
|
|
224
|
+
If False, waits for at least one stream to become active.
|
|
215
225
|
|
|
216
226
|
Returns:
|
|
217
|
-
True if condition is met
|
|
227
|
+
True if the condition is met within the timeout, False otherwise.
|
|
218
228
|
"""
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
return False
|
|
229
|
+
enabled_subscribers = [s for s in self._subscribers.values() if s is not None]
|
|
230
|
+
if not enabled_subscribers:
|
|
231
|
+
logger.warning(f"'{self._name}': No subscribers enabled, cannot wait.")
|
|
232
|
+
return True # No subscribers to wait for
|
|
224
233
|
|
|
225
234
|
if require_all:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
235
|
+
for sub in enabled_subscribers:
|
|
236
|
+
if not sub.wait_for_active(timeout):
|
|
237
|
+
logger.warning(f"'{self._name}': Timed out waiting for subscriber '{sub.name}'.")
|
|
229
238
|
return False
|
|
239
|
+
logger.info(f"'{self._name}': All enabled streams are active.")
|
|
230
240
|
return True
|
|
231
241
|
else:
|
|
232
|
-
# Wait for any subscriber to become active
|
|
233
|
-
import time
|
|
234
242
|
start_time = time.time()
|
|
235
243
|
while time.time() - start_time < timeout:
|
|
236
244
|
if self.is_active():
|
|
245
|
+
logger.info(f"'{self._name}': At least one stream is active.")
|
|
237
246
|
return True
|
|
238
247
|
time.sleep(0.1)
|
|
248
|
+
logger.warning(f"'{self._name}': Timed out waiting for any stream to become active.")
|
|
239
249
|
return False
|
|
240
250
|
|
|
241
|
-
def get_obs(
|
|
242
|
-
|
|
251
|
+
def get_obs(
|
|
252
|
+
self, obs_keys: Optional[list[str]] = None
|
|
253
|
+
) -> Dict[str, Optional[np.ndarray]]:
|
|
254
|
+
"""Get the latest observation data from specified camera streams.
|
|
243
255
|
|
|
244
256
|
Args:
|
|
245
|
-
obs_keys:
|
|
246
|
-
|
|
257
|
+
obs_keys: A list of stream names to retrieve data from (e.g.,
|
|
258
|
+
['left_rgb', 'depth']). If None, retrieves data from all
|
|
259
|
+
enabled streams.
|
|
247
260
|
|
|
248
261
|
Returns:
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
Raises:
|
|
253
|
-
RuntimeError: If depth data is requested but dexsensor is not available.
|
|
262
|
+
A dictionary mapping stream names to their latest image data. The
|
|
263
|
+
image is a numpy array (HxWxC for RGB, HxW for depth) or None if
|
|
264
|
+
no data is available for that stream.
|
|
254
265
|
"""
|
|
255
|
-
|
|
256
|
-
obs_keys = list(self._subscribers.keys())
|
|
257
|
-
|
|
266
|
+
keys_to_fetch = obs_keys or self.available_streams
|
|
258
267
|
obs_out = {}
|
|
259
|
-
for key in
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if subscriber:
|
|
263
|
-
raw_data = subscriber.get_latest_data()
|
|
264
|
-
obs_out[key] = raw_data
|
|
265
|
-
else:
|
|
266
|
-
obs_out[key] = None
|
|
267
|
-
else:
|
|
268
|
-
logger.warning(f"Unknown stream key: {key} for {self._name}")
|
|
269
|
-
|
|
268
|
+
for key in keys_to_fetch:
|
|
269
|
+
subscriber = self._subscribers.get(key)
|
|
270
|
+
obs_out[key] = subscriber.get_latest_data() if subscriber else None
|
|
270
271
|
return obs_out
|
|
271
272
|
|
|
272
|
-
def get_left_rgb(self) -> np.ndarray
|
|
273
|
-
"""Get the latest left RGB
|
|
273
|
+
def get_left_rgb(self) -> Optional[np.ndarray]:
|
|
274
|
+
"""Get the latest image from the left RGB stream.
|
|
274
275
|
|
|
275
276
|
Returns:
|
|
276
|
-
|
|
277
|
+
The latest left RGB image as a numpy array, or None if not available.
|
|
277
278
|
"""
|
|
278
|
-
subscriber = self._subscribers.get(
|
|
279
|
+
subscriber = self._subscribers.get("left_rgb")
|
|
279
280
|
return subscriber.get_latest_data() if subscriber else None
|
|
280
281
|
|
|
281
|
-
def get_right_rgb(self) -> np.ndarray
|
|
282
|
-
"""Get the latest right RGB
|
|
282
|
+
def get_right_rgb(self) -> Optional[np.ndarray]:
|
|
283
|
+
"""Get the latest image from the right RGB stream.
|
|
283
284
|
|
|
284
285
|
Returns:
|
|
285
|
-
|
|
286
|
+
The latest right RGB image as a numpy array, or None if not available.
|
|
286
287
|
"""
|
|
287
|
-
subscriber = self._subscribers.get(
|
|
288
|
+
subscriber = self._subscribers.get("right_rgb")
|
|
288
289
|
return subscriber.get_latest_data() if subscriber else None
|
|
289
290
|
|
|
290
|
-
def get_depth(self) -> np.ndarray
|
|
291
|
-
"""Get the latest depth
|
|
291
|
+
def get_depth(self) -> Optional[np.ndarray]:
|
|
292
|
+
"""Get the latest image from the depth stream.
|
|
292
293
|
|
|
293
|
-
|
|
294
|
-
Latest depth image as numpy array (HxW) if available, None otherwise.
|
|
294
|
+
The depth data is returned as a numpy array with values in meters.
|
|
295
295
|
|
|
296
|
-
|
|
297
|
-
|
|
296
|
+
Returns:
|
|
297
|
+
The latest depth image as a numpy array, or None if not available.
|
|
298
298
|
"""
|
|
299
|
-
subscriber = self._subscribers.get(
|
|
300
|
-
if
|
|
301
|
-
return None
|
|
302
|
-
|
|
303
|
-
# DepthCameraSubscriber already handles decoding
|
|
304
|
-
return subscriber.get_latest_data()
|
|
299
|
+
subscriber = self._subscribers.get("depth")
|
|
300
|
+
return subscriber.get_latest_data() if subscriber else None
|
|
305
301
|
|
|
306
302
|
@property
|
|
307
|
-
def fps(self) ->
|
|
308
|
-
"""Get the current FPS measurement for each stream.
|
|
303
|
+
def fps(self) -> Dict[str, float]:
|
|
304
|
+
"""Get the current FPS measurement for each active stream.
|
|
309
305
|
|
|
310
306
|
Returns:
|
|
311
|
-
|
|
307
|
+
A dictionary mapping stream names to their FPS measurements.
|
|
312
308
|
"""
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
fps_dict[stream_name] = 0.0
|
|
319
|
-
return fps_dict
|
|
309
|
+
return {
|
|
310
|
+
name: sub.fps
|
|
311
|
+
for name, sub in self._subscribers.items()
|
|
312
|
+
if sub is not None
|
|
313
|
+
}
|
|
320
314
|
|
|
321
315
|
@property
|
|
322
316
|
def name(self) -> str:
|
|
@@ -362,3 +356,34 @@ class ZedCameraSensor:
|
|
|
362
356
|
Camera info dictionary if available, None otherwise.
|
|
363
357
|
"""
|
|
364
358
|
return self._camera_info
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def height(self) -> dict[str, int]:
|
|
362
|
+
"""Get the height of the camera image.
|
|
363
|
+
|
|
364
|
+
Returns:
|
|
365
|
+
Height of the camera image.
|
|
366
|
+
"""
|
|
367
|
+
images = self.get_obs()
|
|
368
|
+
return {name: image.shape[0] if image is not None else 0 for name, image in images.items()}
|
|
369
|
+
|
|
370
|
+
@property
|
|
371
|
+
def width(self) -> dict[str, int]:
|
|
372
|
+
"""Get the width of the camera image.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Width of the camera image.
|
|
376
|
+
"""
|
|
377
|
+
images = self.get_obs()
|
|
378
|
+
return {name: image.shape[1] if image is not None else 0 for name, image in images.items()}
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def resolution(self) -> dict[str, tuple[int, int]]:
|
|
383
|
+
"""Get the resolution of the camera image.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
Resolution of the camera image.
|
|
387
|
+
"""
|
|
388
|
+
images = self.get_obs()
|
|
389
|
+
return {name: (image.shape[0], image.shape[1]) if image is not None else (0, 0) for name, image in images.items()}
|
|
@@ -84,16 +84,9 @@ class RPLidarSensor:
|
|
|
84
84
|
Returns:
|
|
85
85
|
Latest scan data dictionary if available, None otherwise.
|
|
86
86
|
Dictionary contains:
|
|
87
|
-
- ranges: Array of range measurements
|
|
88
|
-
- angles: Array of corresponding angles
|
|
89
|
-
-
|
|
90
|
-
- angle_min: Minimum angle of the scan
|
|
91
|
-
- angle_max: Maximum angle of the scan
|
|
92
|
-
- angle_increment: Angular distance between measurements
|
|
93
|
-
- scan_time: Time for a complete scan
|
|
94
|
-
- time_increment: Time between measurements
|
|
95
|
-
- range_min: Minimum range value
|
|
96
|
-
- range_max: Maximum range value
|
|
87
|
+
- ranges: Array of range measurements in meters
|
|
88
|
+
- angles: Array of corresponding angles in radians
|
|
89
|
+
- qualities: Array of quality values (0-255) if available, None otherwise
|
|
97
90
|
"""
|
|
98
91
|
return self._subscriber.get_latest_data()
|
|
99
92
|
|
|
@@ -101,7 +94,7 @@ class RPLidarSensor:
|
|
|
101
94
|
"""Get the latest range measurements.
|
|
102
95
|
|
|
103
96
|
Returns:
|
|
104
|
-
Array of range measurements if available, None otherwise.
|
|
97
|
+
Array of range measurements in meters if available, None otherwise.
|
|
105
98
|
"""
|
|
106
99
|
return self._subscriber.get_ranges()
|
|
107
100
|
|
|
@@ -109,27 +102,17 @@ class RPLidarSensor:
|
|
|
109
102
|
"""Get the latest angle measurements.
|
|
110
103
|
|
|
111
104
|
Returns:
|
|
112
|
-
Array of angle measurements if available, None otherwise.
|
|
105
|
+
Array of angle measurements in radians if available, None otherwise.
|
|
113
106
|
"""
|
|
114
107
|
return self._subscriber.get_angles()
|
|
115
108
|
|
|
116
|
-
def
|
|
117
|
-
"""Get the latest
|
|
109
|
+
def get_qualities(self) -> np.ndarray | None:
|
|
110
|
+
"""Get the latest quality measurements.
|
|
118
111
|
|
|
119
112
|
Returns:
|
|
120
|
-
Array of
|
|
113
|
+
Array of quality values (0-255) if available, None otherwise.
|
|
121
114
|
"""
|
|
122
|
-
return self._subscriber.
|
|
123
|
-
|
|
124
|
-
def get_scan_info(self) -> dict[str, float] | None:
|
|
125
|
-
"""Get scan metadata information.
|
|
126
|
-
|
|
127
|
-
Returns:
|
|
128
|
-
Dictionary with scan metadata if available, None otherwise.
|
|
129
|
-
Contains: angle_min, angle_max, angle_increment, scan_time,
|
|
130
|
-
time_increment, range_min, range_max
|
|
131
|
-
"""
|
|
132
|
-
return self._subscriber.get_scan_info()
|
|
115
|
+
return self._subscriber.get_qualities()
|
|
133
116
|
|
|
134
117
|
def get_point_count(self) -> int:
|
|
135
118
|
"""Get the number of points in the latest scan.
|
|
@@ -142,13 +125,13 @@ class RPLidarSensor:
|
|
|
142
125
|
return len(ranges)
|
|
143
126
|
return 0
|
|
144
127
|
|
|
145
|
-
def
|
|
146
|
-
"""Check if the latest scan data includes
|
|
128
|
+
def has_qualities(self) -> bool:
|
|
129
|
+
"""Check if the latest scan data includes quality information.
|
|
147
130
|
|
|
148
131
|
Returns:
|
|
149
|
-
True if
|
|
132
|
+
True if quality data is available, False otherwise.
|
|
150
133
|
"""
|
|
151
|
-
return self._subscriber.
|
|
134
|
+
return self._subscriber.has_qualities()
|
|
152
135
|
|
|
153
136
|
@property
|
|
154
137
|
def fps(self) -> float:
|
dexcontrol/utils/pb_utils.py
CHANGED
|
@@ -15,10 +15,14 @@ from typing import Any, Literal
|
|
|
15
15
|
|
|
16
16
|
from dexcontrol.proto import dexcontrol_query_pb2
|
|
17
17
|
|
|
18
|
+
TYPE_SOFTWARE_VERSION = dict[
|
|
19
|
+
Literal["hardware_version", "software_version", "main_hash", "compile_time"], Any
|
|
20
|
+
]
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
def software_version_to_dict(
|
|
20
24
|
version_msg: dexcontrol_query_pb2.SoftwareVersion,
|
|
21
|
-
) -> dict[str,
|
|
25
|
+
) -> dict[str, TYPE_SOFTWARE_VERSION]:
|
|
22
26
|
"""Convert a SoftwareVersion protobuf message to a dictionary.
|
|
23
27
|
|
|
24
28
|
Args:
|
|
@@ -32,6 +36,7 @@ def software_version_to_dict(
|
|
|
32
36
|
"hardware_version": value.hardware_version,
|
|
33
37
|
"software_version": value.software_version,
|
|
34
38
|
"main_hash": value.main_hash,
|
|
39
|
+
"compile_time": value.compile_time,
|
|
35
40
|
}
|
|
36
41
|
for key, value in version_msg.firmware_version.items()
|
|
37
42
|
}
|