Skip to content

Commit 8b6c86f

Browse files
authored
Merge pull request #4 from stereolabs/add-resolution-fps-params
Add resolution fps params
2 parents ce41e89 + 417e010 commit 8b6c86f

File tree

6 files changed

+133
-30
lines changed

6 files changed

+133
-30
lines changed

exts/sl.sensor.camera/config/extension.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
[package]
33
# Semantic Versioning is used: https://semver.org/
4-
version = "1.0.2"
4+
version = "1.0.3"
55

66
# Lists people or organizations that are considered the "authors" of the package.
77
authors = ["Stereolabs"]
@@ -13,6 +13,9 @@ description="Streams virtual ZED camera data to the ZED SDK. "
1313
# Path (relative to the root) or content of readme markdown file for UI.
1414
readme = "docs/README.md"
1515

16+
# Path (relative to the root) or content of changelog markdown file for UI.
17+
changelog = "docs/CHANGELOG.md"
18+
1619
# URL of the extension source repository.
1720
repository="https://github.com/stereolabs/zed-isaac-sim"
1821

exts/sl.sensor.camera/docs/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
44

5+
## [1.0.3] - 2024-02-28
6+
- Add FPS and resolution parameters in Isaac Sim GUI
7+
- Add throttling of data fetch to improve performance on low FPS
8+
9+
## [1.0.2] - 2024-02-16
10+
- Improve ZED Camera extension streaming performance
11+
512
## [1.0.1] - 2023-12-19
613
- Update Stereolabs logo
714

exts/sl.sensor.camera/docs/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ Once you press play in the simulation, you can connect the virtual camera to the
2929
Please refer to the [StereoLabs' website for the full documentation](https://www.stereolabs.com/docs/isaac-sim/setting_up_zed_isaac_sim).
3030

3131

32+
### TODO
33+
34+
- ZED Camera extension parameters are not reset after pressing Stop in Isaac Sim UI, but requires the node to be released. TODO: Clean parameters between each play/stop session

exts/sl.sensor.camera/sl/sensor/camera/nodes/SlCameraStreamer.ogn

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@
2323
"uiName": "ZED Camera prim"
2424
}
2525
},
26+
"fps": {
27+
"type" : "uint",
28+
"description": "Camera stream frame rate. Can be either 60, 30 or 15.",
29+
"metadata": {
30+
"uiName": "FPS"
31+
},
32+
"default": 30
33+
},
34+
"resolution": {
35+
"type" : "token",
36+
"description": "Camera stream resolution. Can be either HD1080, HD720 or VGA",
37+
"metadata": {
38+
"allowedTokens": ["HD1080", "HD720", "VGA"]
39+
},
40+
"default" : "HD720"
41+
},
2642
"streaming_port": {
2743
"type": "uint",
2844
"description": "Streaming port - unique per camera",

exts/sl.sensor.camera/sl/sensor/camera/nodes/SlCameraStreamer.py

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
"""
44
import carb
55
from dataclasses import dataclass
6-
# imports needed if you want to create viewports of the camera
7-
# from omni.kit.viewport.utility import get_viewport_from_window_name
8-
# from omni.kit.viewport.utility import create_viewport_window
96
import omni.replicator.core as rep
107
from omni.isaac.core_nodes.bindings import _omni_isaac_core_nodes
118
from omni.isaac.core.prims import XFormPrim
129
from pxr import Vt
1310
from omni.isaac.core.utils.prims import is_prim_path_valid, get_prim_at_path
1411
from omni.isaac.sensor import _sensor
12+
1513
from sl.sensor.camera.pyzed_sim_streamer import ZEDSimStreamer, ZEDSimStreamerParams
1614
import time
1715
import copy
@@ -21,10 +19,9 @@
2119
LEFT_CAMERA_PATH = "/base_link/ZED_X/CameraLeft"
2220
RIGHT_CAMERA_PATH = "/base_link/ZED_X/CameraRight"
2321
IMU_PRIM_PATH = "/base_link/ZED_X/Imu_Sensor"
24-
IMAGE_WIDTH = 1920
25-
IMAGE_HEIGHT = 1080
2622
CHANNELS = 3
27-
23+
24+
2825
class SlCameraStreamer:
2926
"""
3027
Streams camera data to the ZED SDK
@@ -42,6 +39,7 @@ class State:
4239
pyzed_streamer = None
4340
start_time = -1.0
4441
last_timestamp = 0.0
42+
target_fps = 30
4543
camera_prim_name = None
4644
override_simulation_time = False
4745
imu_sensor_interface = None
@@ -55,12 +53,12 @@ def internal_state() -> State:
5553
return SlCameraStreamer.State()
5654

5755
@staticmethod
58-
def get_render_product_path(camera_path :str, render_product_size = [IMAGE_WIDTH, IMAGE_HEIGHT], force_new=True):
56+
def get_render_product_path(camera_path :str, render_product_size = [1280, 720], force_new=True):
5957
"""Helper function to get render product path
6058
6159
Args:
6260
camera_path (str): the path of the camera prim
63-
render_product_size (list, optional): the resolution of the image. Defaults to [IMAGE_WIDTH, IMAGE_HEIGHT].
61+
render_product_size (list, optional): the resolution of the image. Defaults to HD720.
6462
force_new (bool, optional): forces the creation of a new render product. Defaults to True.
6563
6664
Returns:
@@ -71,7 +69,7 @@ def get_render_product_path(camera_path :str, render_product_size = [IMAGE_WIDTH
7169
return render_product_path
7270

7371
@staticmethod
74-
def get_image_data(annotator):
72+
def get_image_data(annotator, data_shape: tuple):
7573
"""Helper function to fetch image data.
7674
7775
Args:
@@ -88,14 +86,22 @@ def get_image_data(annotator):
8886
result = False
8987
return data, result
9088
data = data[:, :, :3]
91-
if not data.shape == (IMAGE_HEIGHT, IMAGE_WIDTH, CHANNELS):
89+
if not data.shape == data_shape:
9290
result = False
9391
except:
9492
return None, False
9593
return data, result
9694

9795
@staticmethod
9896
def check_camera(camera_prim_path : str):
97+
"""Check if the camera properties are valid. If not, set them to default values.
98+
99+
Args:
100+
camera_prim_path (str): the path of the camera prim
101+
102+
Returns:
103+
bool: True if the camera properties are valid, False otherwise
104+
"""
99105
result = False
100106
default_fl, default_apt_h, default_apt_v = 2.208, 5.76, 3.24
101107
default_projection_type = "pinhole"
@@ -119,6 +125,33 @@ def check_camera(camera_prim_path : str):
119125
result = True
120126
return result
121127

128+
@staticmethod
129+
def get_resolution(camera_resolution: str):
130+
"""Get the resolution of the camera
131+
132+
Args:
133+
camera_resolution (str): the resolution name of the camera
134+
135+
Returns:
136+
list: the resolution of the camera
137+
"""
138+
if camera_resolution == "HD1080":
139+
result = [1920, 1080]
140+
elif camera_resolution == "HD720":
141+
result = [1280, 720]
142+
elif camera_resolution == "VGA":
143+
result = [672, 376]
144+
else:
145+
result = None
146+
return result
147+
148+
@staticmethod
149+
def check_frame_rate(camera_frame_rate: int):
150+
if camera_frame_rate not in [15, 30, 60]:
151+
carb.log_warn(f"Invalid frame rate passed: {camera_frame_rate}. Defaulting to 30.")
152+
return 30
153+
return camera_frame_rate
154+
122155

123156
@staticmethod
124157
def compute(db) -> bool:
@@ -129,7 +162,7 @@ def compute(db) -> bool:
129162
if db.inputs.camera_prim is None:
130163
carb.log_error("Invalid Camera prim")
131164
return
132-
165+
133166
# Check port
134167
port = db.inputs.streaming_port
135168
if port <= 0 or port %2 == 1:
@@ -152,21 +185,28 @@ def compute(db) -> bool:
152185
if not res:
153186
carb.log_warn(f"[{db.inputs.camera_prim[0].GetPrimPath()}] Invalid or non existing zed camera, try to re-import your camera prim.")
154187

188+
# Check resolution and retrieve width and height
189+
resolution = SlCameraStreamer.get_resolution(db.inputs.resolution)
190+
if resolution is None:
191+
resolution = [1280, 720]
192+
carb.log_warn(f"Invalid resolution passed. Defaulting to HD720.")
193+
194+
# Check frame rate
195+
db.internal_state.target_fps = SlCameraStreamer.check_frame_rate(db.inputs.fps)
196+
155197
if (db.internal_state.annotator_left is None):
156-
render_product_path_left = SlCameraStreamer.get_render_product_path(left_cam_path)
198+
render_product_path_left = SlCameraStreamer.get_render_product_path(left_cam_path, render_product_size=resolution)
157199
db.internal_state.annotator_left = rep.AnnotatorRegistry.get_annotator("rgb", device="cuda", do_array_copy=False)
158200
db.internal_state.annotator_left.attach([render_product_path_left])
159201

160202
if (db.internal_state.annotator_right is None):
161-
render_product_path_right = SlCameraStreamer.get_render_product_path(right_cam_path)
203+
render_product_path_right = SlCameraStreamer.get_render_product_path(right_cam_path, render_product_size=resolution)
162204
db.internal_state.annotator_right = rep.AnnotatorRegistry.get_annotator("rgb", device="cuda", do_array_copy=False)
163205
db.internal_state.annotator_right.attach([render_product_path_right])
164206

165207
db.internal_state.imu_prim_path = db.inputs.camera_prim[0].pathString + IMU_PRIM_PATH
166208
db.internal_state.imu_prim = XFormPrim(prim_path=db.internal_state.imu_prim_path)
167209

168-
carb.settings.get_settings().set("/omni/replicator/captureOnPlay", False)
169-
170210
# Setup streamer parameters
171211
db.internal_state.pyzed_streamer = ZEDSimStreamer()
172212

@@ -178,8 +218,9 @@ def compute(db) -> bool:
178218
carb.log_warn(f"Invalid serial number passed. Defaulting to: {serial_number}.")
179219

180220
streamer_params = ZEDSimStreamerParams()
181-
streamer_params.image_width = IMAGE_WIDTH
182-
streamer_params.image_height = IMAGE_HEIGHT
221+
streamer_params.image_width = resolution[0]
222+
streamer_params.image_height = resolution[1]
223+
streamer_params.fps = db.internal_state.target_fps
183224
streamer_params.alpha_channel_included = False
184225
streamer_params.rgb_encoded = True
185226
streamer_params.serial_number = serial_number
@@ -190,24 +231,38 @@ def compute(db) -> bool:
190231
# set state to initialized
191232
carb.log_info(f"Streaming camera {db.internal_state.camera_prim_name} at port {port} and using serial number {serial_number}.")
192233
db.internal_state.invalid_images_count = 0
234+
db.internal_state.last_timestamp = 0.0
235+
db.internal_state.data_shape = (resolution[1], resolution[0], CHANNELS)
193236
db.internal_state.initialized = True
194237

195238
try:
196239
ts : int = 0
197240
if db.internal_state.initialized is True:
198241
left, right = None, None
199242
current_time = db.internal_state.core_nodes_interface.get_sim_time()
243+
244+
# Reset last_timestamp between different play sessions
245+
if db.internal_state.last_timestamp > current_time:
246+
db.internal_state.last_timestamp = current_time
247+
248+
delta_time = current_time - db.internal_state.last_timestamp
249+
250+
# Skip data fetch if the time between frames is too short
251+
if delta_time < 1.0 / db.internal_state.target_fps:
252+
return False
200253

201254
# we fetch image data from annotators - we do it sequentially to avoid fetching both if one fails
202-
left, res = SlCameraStreamer.get_image_data(db.internal_state.annotator_left)
255+
left, res = SlCameraStreamer.get_image_data(db.internal_state.annotator_left,
256+
db.internal_state.data_shape)
203257
if res == False:
204258
db.internal_state.invalid_images_count +=1
205259
if db.internal_state.invalid_images_count >= 10:
206260
carb.log_warn(f"{db.internal_state.camera_prim_name} - Left camera retrieved unexpected "
207261
"data shape, skipping frame.")
208262
return False # no need to continue compute
209263

210-
right, res = SlCameraStreamer.get_image_data(db.internal_state.annotator_right)
264+
right, res = SlCameraStreamer.get_image_data(db.internal_state.annotator_right,
265+
db.internal_state.data_shape)
211266
if res == False:
212267
db.internal_state.invalid_images_count +=1
213268
if db.internal_state.invalid_images_count >= 10:

exts/sl.sensor.camera/sl/sensor/camera/ogn/SlCameraStreamerDatabase.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import omni.graph.core._omni_graph_core as _og
88
import omni.graph.tools.ogn as ogn
99
import traceback
10-
import carb
1110
import sys
1211
class SlCameraStreamerDatabase(og.Database):
1312
"""Helper class providing simplified access to data on nodes of type sl.sensor.camera.ZED_Camera
@@ -19,6 +18,8 @@ class SlCameraStreamerDatabase(og.Database):
1918
Inputs:
2019
inputs.camera_prim
2120
inputs.exec_in
21+
inputs.resolution
22+
inputs.fps
2223
inputs.serial_number
2324
inputs.streaming_port
2425
inputs.use_system_time
@@ -33,6 +34,8 @@ class SlCameraStreamerDatabase(og.Database):
3334
INTERFACE = og.Database._get_interface([
3435
('inputs:camera_prim', 'bundle', 0, 'ZED Camera prim', 'ZED Camera prim used to stream data', {}, True, None, False, ''),
3536
('inputs:exec_in', 'execution', 0, 'ExecIn', 'Triggers execution', {ogn.MetadataKeys.DEFAULT: '0'}, True, 0, False, ''),
37+
('inputs:resolution', 'token', 0, 'Resolution', 'Camera stream resolution. Can be either HD1080, HD720 or VGA', {ogn.MetadataKeys.DEFAULT: 'HD720'}, True, "HD720", False, ''),
38+
('inputs:fps', 'uint', 0, 'FPS', 'Camera stream frame rate. Can be either 60, 30 or 15', {ogn.MetadataKeys.DEFAULT: '30'}, True, '30', False, ''),
3639
('inputs:serial_number', 'uint', 0, 'Serial number', 'Serial number (identification) of the camera to stream, can be left to default. It must be of one of the compatible values: 20976320, 29123828, 25626933, 27890353, 25263213, 21116066, 27800035, 27706147', {ogn.MetadataKeys.DEFAULT: '20976320'}, True, 20976320, False, ''),
3740
('inputs:streaming_port', 'uint', 0, 'Streaming port', 'Streaming port - unique per camera', {ogn.MetadataKeys.DEFAULT: '30000'}, True, 30000, False, ''),
3841
('inputs:use_system_time', 'bool', 0, 'Use system time', 'Override simulation time with system time for image timestamps', {ogn.MetadataKeys.DEFAULT: 'false'}, True, False, False, ''),
@@ -45,15 +48,15 @@ def _populate_role_data(cls):
4548
role_data.inputs.exec_in = og.Database.ROLE_EXECUTION
4649
return role_data
4750
class ValuesForInputs(og.DynamicAttributeAccess):
48-
LOCAL_PROPERTY_NAMES = {"exec_in", "serial_number", "streaming_port", "use_system_time", "_setting_locked", "_batchedReadAttributes", "_batchedReadValues"}
51+
LOCAL_PROPERTY_NAMES = {"exec_in", "resolution", "fps", "serial_number", "streaming_port", "use_system_time", "_setting_locked", "_batchedReadAttributes", "_batchedReadValues"}
4952
"""Helper class that creates natural hierarchical access to input attributes"""
5053
def __init__(self, node: og.Node, attributes, dynamic_attributes: og.DynamicAttributeInterface):
5154
"""Initialize simplified access for the attribute data"""
5255
context = node.get_graph().get_default_graph_context()
5356
super().__init__(context, node, attributes, dynamic_attributes)
5457
self.__bundles = og.BundleContainer(context, node, attributes, [], read_only=True, gpu_ptr_kinds={})
55-
self._batchedReadAttributes = [self._attributes.exec_in, self._attributes.serial_number, self._attributes.streaming_port, self._attributes.use_system_time]
56-
self._batchedReadValues = [0, 20976320, 30000, False]
58+
self._batchedReadAttributes = [self._attributes.exec_in, self._attributes.resolution, self._attributes.fps, self._attributes.serial_number, self._attributes.streaming_port, self._attributes.use_system_time]
59+
self._batchedReadValues = [0, "HD720", "30", 20976320, 30000, False]
5760

5861
@property
5962
def camera_prim(self) -> og.BundleContents:
@@ -69,28 +72,44 @@ def exec_in(self, value):
6972
self._batchedReadValues[0] = value
7073

7174
@property
72-
def serial_number(self):
75+
def resolution(self):
7376
return self._batchedReadValues[1]
7477

78+
@resolution.setter
79+
def resolution(self, value):
80+
self._batchedReadValues[1] = value
81+
82+
@property
83+
def fps(self):
84+
return self._batchedReadValues[2]
85+
86+
@fps.setter
87+
def fps(self, value):
88+
self._batchedReadValues[2] = value
89+
90+
@property
91+
def serial_number(self):
92+
return self._batchedReadValues[3]
93+
7594
@serial_number.setter
7695
def serial_number(self, value):
77-
self._batchedReadValues[1] = value
96+
self._batchedReadValues[3] = value
7897

7998
@property
8099
def streaming_port(self):
81-
return self._batchedReadValues[2]
100+
return self._batchedReadValues[4]
82101

83102
@streaming_port.setter
84103
def streaming_port(self, value):
85-
self._batchedReadValues[2] = value
104+
self._batchedReadValues[4] = value
86105

87106
@property
88107
def use_system_time(self):
89-
return self._batchedReadValues[3]
108+
return self._batchedReadValues[5]
90109

91110
@use_system_time.setter
92111
def use_system_time(self, value):
93-
self._batchedReadValues[3] = value
112+
self._batchedReadValues[5] = value
94113

95114
def __getattr__(self, item: str):
96115
if item in self.LOCAL_PROPERTY_NAMES:

0 commit comments

Comments
 (0)