dexcontrol 0.2.1__py3-none-any.whl → 0.2.3__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/__init__.py +14 -3
- dexcontrol/apps/dualsense_teleop_base.py +16 -11
- dexcontrol/config/__init__.py +10 -5
- dexcontrol/config/core/__init__.py +8 -3
- dexcontrol/config/core/arm.py +8 -3
- dexcontrol/config/core/chassis.py +10 -5
- dexcontrol/config/core/hand.py +14 -9
- dexcontrol/config/core/head.py +8 -3
- dexcontrol/config/core/misc.py +8 -3
- dexcontrol/config/core/torso.py +8 -3
- dexcontrol/config/sensors/__init__.py +8 -3
- dexcontrol/config/sensors/cameras/__init__.py +9 -4
- dexcontrol/config/sensors/cameras/rgb_camera.py +18 -5
- dexcontrol/config/sensors/cameras/zed_camera.py +36 -0
- dexcontrol/config/sensors/imu/__init__.py +10 -5
- dexcontrol/config/sensors/imu/chassis_imu.py +21 -0
- dexcontrol/config/sensors/imu/zed_imu.py +21 -0
- dexcontrol/config/sensors/lidar/__init__.py +8 -3
- dexcontrol/config/sensors/lidar/rplidar.py +9 -3
- dexcontrol/config/sensors/ultrasonic/__init__.py +8 -3
- dexcontrol/config/sensors/ultrasonic/ultrasonic.py +9 -3
- dexcontrol/config/sensors/vega_sensors.py +34 -21
- dexcontrol/config/vega.py +14 -6
- dexcontrol/core/__init__.py +9 -0
- dexcontrol/core/arm.py +21 -6
- dexcontrol/core/chassis.py +8 -3
- dexcontrol/core/component.py +26 -6
- dexcontrol/core/hand.py +8 -3
- dexcontrol/core/head.py +18 -3
- dexcontrol/core/misc.py +94 -16
- dexcontrol/core/torso.py +8 -3
- dexcontrol/proto/dexcontrol_msg_pb2.py +17 -15
- dexcontrol/proto/dexcontrol_msg_pb2.pyi +24 -0
- dexcontrol/robot.py +82 -28
- dexcontrol/sensors/__init__.py +13 -8
- dexcontrol/sensors/camera/__init__.py +11 -6
- dexcontrol/sensors/camera/rgb_camera.py +33 -24
- dexcontrol/sensors/camera/zed_camera.py +364 -0
- dexcontrol/sensors/imu/__init__.py +13 -8
- dexcontrol/sensors/imu/chassis_imu.py +155 -0
- dexcontrol/sensors/imu/{nine_axis_imu.py → zed_imu.py} +41 -26
- dexcontrol/sensors/lidar/__init__.py +11 -1
- dexcontrol/sensors/lidar/rplidar.py +8 -3
- dexcontrol/sensors/manager.py +22 -9
- dexcontrol/sensors/ultrasonic.py +8 -3
- dexcontrol/utils/__init__.py +8 -3
- dexcontrol/utils/constants.py +10 -0
- dexcontrol/utils/io_utils.py +8 -3
- dexcontrol/utils/motion_utils.py +8 -3
- dexcontrol/utils/os_utils.py +23 -4
- dexcontrol/utils/pb_utils.py +8 -3
- dexcontrol/utils/rate_limiter.py +8 -3
- dexcontrol/utils/rtc_utils.py +144 -0
- dexcontrol/utils/subscribers/__init__.py +11 -3
- dexcontrol/utils/subscribers/base.py +26 -5
- dexcontrol/utils/subscribers/camera.py +10 -6
- dexcontrol/utils/subscribers/decoders.py +8 -3
- dexcontrol/utils/subscribers/generic.py +8 -3
- dexcontrol/utils/subscribers/imu.py +8 -3
- dexcontrol/utils/subscribers/lidar.py +8 -3
- dexcontrol/utils/subscribers/protobuf.py +8 -3
- dexcontrol/utils/subscribers/rtc.py +315 -0
- dexcontrol/utils/timer.py +8 -3
- dexcontrol/utils/trajectory_utils.py +8 -3
- dexcontrol/utils/viz_utils.py +8 -3
- dexcontrol/utils/zenoh_utils.py +83 -0
- dexcontrol-0.2.3.dist-info/METADATA +265 -0
- dexcontrol-0.2.3.dist-info/RECORD +72 -0
- {dexcontrol-0.2.1.dist-info → dexcontrol-0.2.3.dist-info}/WHEEL +1 -2
- dexcontrol-0.2.3.dist-info/licenses/LICENSE +184 -0
- dexcontrol/config/sensors/cameras/gemini_camera.py +0 -16
- dexcontrol/config/sensors/imu/gemini_imu.py +0 -15
- dexcontrol/config/sensors/imu/nine_axis_imu.py +0 -15
- dexcontrol/sensors/camera/gemini_camera.py +0 -139
- dexcontrol/sensors/imu/gemini_imu.py +0 -139
- dexcontrol/utils/reset_orbbec_camera_usb.py +0 -98
- dexcontrol-0.2.1.dist-info/METADATA +0 -369
- dexcontrol-0.2.1.dist-info/RECORD +0 -72
- dexcontrol-0.2.1.dist-info/licenses/LICENSE +0 -188
- dexcontrol-0.2.1.dist-info/licenses/NOTICE +0 -13
- dexcontrol-0.2.1.dist-info/top_level.txt +0 -1
dexcontrol/robot.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Main robot interface module.
|
|
7
12
|
|
|
@@ -36,6 +41,7 @@ import omegaconf
|
|
|
36
41
|
import zenoh
|
|
37
42
|
from loguru import logger
|
|
38
43
|
from rich.console import Console
|
|
44
|
+
from rich.table import Table
|
|
39
45
|
|
|
40
46
|
import dexcontrol
|
|
41
47
|
from dexcontrol.config.vega import VegaConfig, get_vega_config
|
|
@@ -106,7 +112,6 @@ def _register_signal_handlers() -> None:
|
|
|
106
112
|
signal.signal(signal.SIGHUP, _signal_handler)
|
|
107
113
|
|
|
108
114
|
_signal_handlers_registered = True
|
|
109
|
-
logger.debug("Signal handlers registered for graceful robot shutdown")
|
|
110
115
|
|
|
111
116
|
|
|
112
117
|
class ComponentConfig(omegaconf.DictConfig):
|
|
@@ -178,7 +183,7 @@ class Robot:
|
|
|
178
183
|
"""Initializes the Robot with the given configuration.
|
|
179
184
|
|
|
180
185
|
Args:
|
|
181
|
-
robot_model: Optional robot variant name (e.g., "
|
|
186
|
+
robot_model: Optional robot variant name (e.g., "vega-rc2", "vega-1").
|
|
182
187
|
If configs is None, this will be used to get the appropriate config.
|
|
183
188
|
Ignored if configs is provided.
|
|
184
189
|
configs: Configuration parameters for all robot components.
|
|
@@ -192,6 +197,8 @@ class Robot:
|
|
|
192
197
|
RuntimeError: If any critical component fails to become active within timeout.
|
|
193
198
|
ValueError: If robot_model is invalid or configs cannot be loaded.
|
|
194
199
|
"""
|
|
200
|
+
self._components: list[RobotComponent] = []
|
|
201
|
+
|
|
195
202
|
if robot_model is None:
|
|
196
203
|
robot_model = get_robot_model()
|
|
197
204
|
self._robot_model: Final[str] = robot_model
|
|
@@ -208,7 +215,6 @@ class Robot:
|
|
|
208
215
|
except Exception as e:
|
|
209
216
|
raise RuntimeError(f"Failed to initialize zenoh session: {e}") from e
|
|
210
217
|
|
|
211
|
-
self._components: list[RobotComponent] = []
|
|
212
218
|
self._robot_name: Final[str] = os.getenv(ROBOT_NAME_ENV_VAR, "robot")
|
|
213
219
|
self._pv_components: Final[list[str]] = [
|
|
214
220
|
"left_hand",
|
|
@@ -234,18 +240,19 @@ class Robot:
|
|
|
234
240
|
self.shutdown() # Clean up on initialization failure
|
|
235
241
|
raise RuntimeError(f"Failed to initialize robot components: {e}") from e
|
|
236
242
|
|
|
243
|
+
# Ensure all components are active
|
|
237
244
|
try:
|
|
238
|
-
self.
|
|
245
|
+
self._wait_for_components()
|
|
239
246
|
except Exception as e:
|
|
240
247
|
self.shutdown() # Clean up on initialization failure
|
|
241
|
-
raise RuntimeError(f"Failed to
|
|
248
|
+
raise RuntimeError(f"Failed to activate components: {e}") from e
|
|
242
249
|
|
|
243
|
-
# Ensure all components are active
|
|
244
250
|
try:
|
|
245
|
-
self.
|
|
251
|
+
self.sensors = Sensors(self._configs.sensors, self._zenoh_session)
|
|
252
|
+
self.sensors.wait_for_all_active()
|
|
246
253
|
except Exception as e:
|
|
247
254
|
self.shutdown() # Clean up on initialization failure
|
|
248
|
-
raise RuntimeError(f"Failed to
|
|
255
|
+
raise RuntimeError(f"Failed to initialize sensors: {e}") from e
|
|
249
256
|
|
|
250
257
|
# Set default modes
|
|
251
258
|
try:
|
|
@@ -306,12 +313,17 @@ class Robot:
|
|
|
306
313
|
Args:
|
|
307
314
|
robot_model: The robot model being initialized.
|
|
308
315
|
"""
|
|
309
|
-
|
|
310
|
-
|
|
316
|
+
console = Console()
|
|
317
|
+
table = Table(show_header=False)
|
|
318
|
+
table.add_column(style="cyan", no_wrap=True)
|
|
319
|
+
table.add_column(style="white")
|
|
320
|
+
|
|
321
|
+
table.add_row("Robot Name", str(self._robot_name))
|
|
311
322
|
if robot_model:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
323
|
+
table.add_row("Robot Model", str(robot_model))
|
|
324
|
+
table.add_row("Communication Config", str(dexcontrol.COMM_CFG_PATH))
|
|
325
|
+
|
|
326
|
+
console.print(table)
|
|
315
327
|
|
|
316
328
|
def _init_components(self, config_dict: dict[str, Any]) -> None:
|
|
317
329
|
"""Initialize robot components from configuration.
|
|
@@ -502,38 +514,80 @@ class Robot:
|
|
|
502
514
|
except Exception: # pylint: disable=broad-except
|
|
503
515
|
pass # WeakSet may already have removed it
|
|
504
516
|
|
|
505
|
-
#
|
|
506
|
-
for component in
|
|
517
|
+
# First, stop all components that have stop methods to halt ongoing operations
|
|
518
|
+
for component in self._components:
|
|
507
519
|
if component is not None:
|
|
508
520
|
try:
|
|
509
|
-
component
|
|
521
|
+
if hasattr(component, "stop"):
|
|
522
|
+
method = getattr(component, "stop")
|
|
523
|
+
if callable(method):
|
|
524
|
+
method()
|
|
510
525
|
except Exception as e: # pylint: disable=broad-except
|
|
511
526
|
logger.error(
|
|
512
|
-
f"Error
|
|
527
|
+
f"Error stopping component {component.__class__.__name__}: {e}"
|
|
513
528
|
)
|
|
514
529
|
|
|
530
|
+
# Longer delay to ensure all stop operations complete and background threads settle
|
|
531
|
+
time.sleep(0.3)
|
|
532
|
+
|
|
533
|
+
# Shutdown sensors first (they may have background threads)
|
|
515
534
|
try:
|
|
516
535
|
self.sensors.shutdown()
|
|
536
|
+
# Give extra time for all sensor subscribers to undeclare cleanly
|
|
537
|
+
time.sleep(0.5)
|
|
517
538
|
except Exception as e: # pylint: disable=broad-except
|
|
518
539
|
logger.error(f"Error shutting down sensors: {e}")
|
|
519
540
|
|
|
520
|
-
#
|
|
541
|
+
# Additional delay for sensor shutdown to complete
|
|
542
|
+
time.sleep(0.2)
|
|
543
|
+
|
|
544
|
+
# Shutdown components in reverse order
|
|
545
|
+
for component in reversed(self._components):
|
|
546
|
+
if component is not None:
|
|
547
|
+
try:
|
|
548
|
+
component.shutdown()
|
|
549
|
+
# Small delay between each component shutdown to prevent race conditions
|
|
550
|
+
time.sleep(0.05)
|
|
551
|
+
except Exception as e: # pylint: disable=broad-except
|
|
552
|
+
logger.error(
|
|
553
|
+
f"Error shutting down component {component.__class__.__name__}: {e}"
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
# Longer delay to allow all component shutdown to complete
|
|
557
|
+
time.sleep(0.5)
|
|
558
|
+
|
|
559
|
+
# Enhanced Zenoh session close with better synchronization
|
|
560
|
+
zenoh_close_success = False
|
|
561
|
+
|
|
521
562
|
def _close_zenoh_session():
|
|
522
563
|
"""Close zenoh session in a separate thread."""
|
|
564
|
+
nonlocal zenoh_close_success
|
|
523
565
|
try:
|
|
566
|
+
# Give Zenoh even more time to clean up internal resources and settle threads
|
|
567
|
+
time.sleep(0.5)
|
|
524
568
|
self._zenoh_session.close()
|
|
569
|
+
zenoh_close_success = True
|
|
525
570
|
except Exception as e: # pylint: disable=broad-except
|
|
526
|
-
logger.
|
|
571
|
+
logger.warning(f"Zenoh session close error: {e}")
|
|
527
572
|
|
|
528
|
-
|
|
573
|
+
# Use non-daemon thread for proper cleanup
|
|
574
|
+
close_thread = threading.Thread(target=_close_zenoh_session, daemon=False)
|
|
529
575
|
close_thread.start()
|
|
530
576
|
|
|
531
|
-
# Wait for close with timeout
|
|
532
|
-
close_thread.join(timeout=0
|
|
577
|
+
# Wait for close with extended timeout to avoid race conditions
|
|
578
|
+
close_thread.join(timeout=3.0)
|
|
579
|
+
|
|
533
580
|
if close_thread.is_alive():
|
|
534
|
-
logger.warning(
|
|
535
|
-
|
|
536
|
-
|
|
581
|
+
logger.warning(
|
|
582
|
+
"Zenoh session close timed out after 5s, continuing shutdown"
|
|
583
|
+
)
|
|
584
|
+
# Force daemon mode so it doesn't block process exit
|
|
585
|
+
close_thread.daemon = True
|
|
586
|
+
elif zenoh_close_success:
|
|
587
|
+
logger.debug("Zenoh session closed cleanly")
|
|
588
|
+
|
|
589
|
+
# Extended final delay to allow any remaining Zenoh background threads to finish
|
|
590
|
+
time.sleep(0.8)
|
|
537
591
|
|
|
538
592
|
logger.info("Robot shutdown complete")
|
|
539
593
|
|
dexcontrol/sensors/__init__.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Sensor implementations for dexcontrol.
|
|
7
12
|
|
|
@@ -10,10 +15,10 @@ using Zenoh subscribers for data communication.
|
|
|
10
15
|
"""
|
|
11
16
|
|
|
12
17
|
# Import camera sensors
|
|
13
|
-
from .camera import
|
|
18
|
+
from .camera import RGBCameraSensor, ZedCameraSensor
|
|
14
19
|
|
|
15
20
|
# Import IMU sensors
|
|
16
|
-
from .imu import
|
|
21
|
+
from .imu import ChassisIMUSensor, ZedIMUSensor
|
|
17
22
|
|
|
18
23
|
# Import other sensors
|
|
19
24
|
from .lidar import RPLidarSensor
|
|
@@ -25,11 +30,11 @@ from .ultrasonic import UltrasonicSensor
|
|
|
25
30
|
__all__ = [
|
|
26
31
|
# Camera sensors
|
|
27
32
|
"RGBCameraSensor",
|
|
28
|
-
"
|
|
33
|
+
"ZedCameraSensor",
|
|
29
34
|
|
|
30
35
|
# IMU sensors
|
|
31
|
-
"
|
|
32
|
-
"
|
|
36
|
+
"ChassisIMUSensor",
|
|
37
|
+
"ZedIMUSensor",
|
|
33
38
|
|
|
34
39
|
# Other sensors
|
|
35
40
|
"RPLidarSensor",
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
5
10
|
|
|
6
11
|
"""Camera sensor implementations using Zenoh subscribers.
|
|
7
12
|
|
|
8
13
|
This module provides camera sensor classes that use the specialized camera
|
|
9
|
-
subscribers for RGB and RGBD
|
|
14
|
+
subscribers for RGB and RGBD camera data, matching the dexsensor structure.
|
|
10
15
|
"""
|
|
11
16
|
|
|
12
|
-
from .gemini_camera import GeminiCameraSensor
|
|
13
17
|
from .rgb_camera import RGBCameraSensor
|
|
18
|
+
from .zed_camera import ZedCameraSensor
|
|
14
19
|
|
|
15
20
|
__all__ = [
|
|
16
21
|
"RGBCameraSensor",
|
|
17
|
-
"
|
|
22
|
+
"ZedCameraSensor",
|
|
18
23
|
]
|
|
@@ -1,27 +1,39 @@
|
|
|
1
|
-
# Copyright (
|
|
1
|
+
# Copyright (C) 2025 Dexmate Inc.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
3
|
+
# This software is dual-licensed:
|
|
4
|
+
#
|
|
5
|
+
# 1. GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
6
|
+
# See LICENSE-AGPL for details
|
|
7
|
+
#
|
|
8
|
+
# 2. Commercial License
|
|
9
|
+
# For commercial licensing terms, contact: contact@dexmate.ai
|
|
10
|
+
|
|
11
|
+
"""RGB camera sensor implementation using RTC subscriber with Zenoh query."""
|
|
5
12
|
|
|
6
|
-
|
|
13
|
+
import logging
|
|
7
14
|
|
|
8
15
|
import numpy as np
|
|
9
16
|
import zenoh
|
|
10
17
|
|
|
11
|
-
from dexcontrol.utils.
|
|
18
|
+
from dexcontrol.utils.rtc_utils import create_rtc_subscriber_with_config
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
12
21
|
|
|
13
22
|
|
|
14
23
|
class RGBCameraSensor:
|
|
15
|
-
"""RGB camera sensor using
|
|
24
|
+
"""RGB camera sensor using RTC subscriber.
|
|
16
25
|
|
|
17
|
-
This sensor provides RGB image data from a camera using
|
|
18
|
-
|
|
26
|
+
This sensor provides RGB image data from a camera using RTC.
|
|
27
|
+
It first queries Zenoh for RTC connection information, then creates
|
|
28
|
+
a RTC subscriber for efficient data handling.
|
|
19
29
|
"""
|
|
20
30
|
|
|
21
31
|
def __init__(
|
|
22
32
|
self,
|
|
23
33
|
configs,
|
|
24
34
|
zenoh_session: zenoh.Session,
|
|
35
|
+
*args,
|
|
36
|
+
**kwargs,
|
|
25
37
|
) -> None:
|
|
26
38
|
"""Initialize the RGB camera sensor.
|
|
27
39
|
|
|
@@ -31,18 +43,23 @@ class RGBCameraSensor:
|
|
|
31
43
|
"""
|
|
32
44
|
self._name = configs.name
|
|
33
45
|
|
|
34
|
-
# Create the
|
|
35
|
-
self._subscriber =
|
|
36
|
-
topic=configs.topic,
|
|
46
|
+
# Create the RTC subscriber using the utility function
|
|
47
|
+
self._subscriber = create_rtc_subscriber_with_config(
|
|
37
48
|
zenoh_session=zenoh_session,
|
|
49
|
+
config=configs.subscriber_config.rgb,
|
|
38
50
|
name=f"{self._name}_subscriber",
|
|
39
51
|
enable_fps_tracking=configs.enable_fps_tracking,
|
|
40
52
|
fps_log_interval=configs.fps_log_interval,
|
|
41
53
|
)
|
|
42
54
|
|
|
55
|
+
if self._subscriber is None:
|
|
56
|
+
logger.warning(f"Failed to create RTC subscriber for {self._name}")
|
|
57
|
+
# Continue initialization without subscriber
|
|
58
|
+
|
|
43
59
|
def shutdown(self) -> None:
|
|
44
60
|
"""Shutdown the camera sensor."""
|
|
45
|
-
self._subscriber
|
|
61
|
+
if self._subscriber:
|
|
62
|
+
self._subscriber.shutdown()
|
|
46
63
|
|
|
47
64
|
def is_active(self) -> bool:
|
|
48
65
|
"""Check if the camera sensor is actively receiving data.
|
|
@@ -50,7 +67,7 @@ class RGBCameraSensor:
|
|
|
50
67
|
Returns:
|
|
51
68
|
True if receiving data, False otherwise.
|
|
52
69
|
"""
|
|
53
|
-
return self._subscriber.is_active()
|
|
70
|
+
return self._subscriber.is_active() if self._subscriber else False
|
|
54
71
|
|
|
55
72
|
def wait_for_active(self, timeout: float = 5.0) -> bool:
|
|
56
73
|
"""Wait for the camera sensor to start receiving data.
|
|
@@ -61,7 +78,7 @@ class RGBCameraSensor:
|
|
|
61
78
|
Returns:
|
|
62
79
|
True if sensor becomes active, False if timeout is reached.
|
|
63
80
|
"""
|
|
64
|
-
return self._subscriber.wait_for_active(timeout)
|
|
81
|
+
return self._subscriber.wait_for_active(timeout) if self._subscriber else False
|
|
65
82
|
|
|
66
83
|
def get_obs(self) -> np.ndarray | None:
|
|
67
84
|
"""Get the latest RGB image data.
|
|
@@ -69,15 +86,7 @@ class RGBCameraSensor:
|
|
|
69
86
|
Returns:
|
|
70
87
|
Latest RGB image as numpy array (HxWxC) if available, None otherwise.
|
|
71
88
|
"""
|
|
72
|
-
return self._subscriber.get_latest_data()
|
|
73
|
-
|
|
74
|
-
def get_rgb_image(self) -> np.ndarray | None:
|
|
75
|
-
"""Get the latest RGB image.
|
|
76
|
-
|
|
77
|
-
Returns:
|
|
78
|
-
Latest RGB image as numpy array (HxWxC) if available, None otherwise.
|
|
79
|
-
"""
|
|
80
|
-
return self._subscriber.get_latest_image()
|
|
89
|
+
return self._subscriber.get_latest_data() if self._subscriber else None
|
|
81
90
|
|
|
82
91
|
@property
|
|
83
92
|
def fps(self) -> float:
|
|
@@ -86,7 +95,7 @@ class RGBCameraSensor:
|
|
|
86
95
|
Returns:
|
|
87
96
|
Current frames per second measurement.
|
|
88
97
|
"""
|
|
89
|
-
return self._subscriber.fps
|
|
98
|
+
return self._subscriber.fps if self._subscriber else 0.0
|
|
90
99
|
|
|
91
100
|
@property
|
|
92
101
|
def name(self) -> str:
|