Isaac Sim Synthetic Data Generation
Chapter Objectives
- Understand synthetic data generation principles in Isaac Sim
- Create diverse training datasets for AI models
- Implement domain randomization techniques
- Generate multi-modal sensor data
- Optimize data generation pipelines for efficiency
Introduction to Synthetic Data Generation
Synthetic data generation is a critical component of modern AI development, especially for robotics applications where real-world data collection can be expensive, time-consuming, or dangerous. Isaac Sim provides powerful tools for generating high-quality synthetic data that can be used to train computer vision, perception, and control models.
Why Synthetic Data for Robotics?
- Safety: Train models on dangerous scenarios without risk
- Cost-Effectiveness: Reduce need for expensive real-world data collection
- Variety: Generate diverse scenarios and edge cases
- Annotation: Perfect ground truth annotations automatically
- Control: Precise control over environmental conditions
- Scalability: Generate large datasets quickly
Isaac Sim's Synthetic Data Capabilities
- Photorealistic Rendering: NVIDIA RTX technology for realistic images
- Multi-Modal Sensors: Cameras, LiDAR, Radar, IMU, Force/Torque sensors
- Domain Randomization: Systematic variation of appearance and physics
- Automatic Annotation: Semantic segmentation, instance segmentation, depth maps
- Large-Scale Generation: Distributed rendering capabilities
Synthetic Data Generation Pipeline
Basic Data Generation Framework
# python/synthetic_data_generator.py
import omni
from pxr import Usd, UsdGeom, UsdShade, Sdf, Gf, UsdPhysics
import numpy as np
import cv2
from PIL import Image
import json
import os
import random
from omni.isaac.core import World
from omni.isaac.sensor import Camera
from omni.isaac.range_sensor import LidarRtx
from omni.isaac.core.utils.stage import add_reference_to_stage
from omni.isaac.core.utils.prims import get_prim_at_path
from omni.isaac.core.utils.nucleus import get_assets_root_path
import carb
from typing import List, Dict, Tuple, Optional
class SyntheticDataGenerator:
def __init__(self, output_dir: str = "synthetic_data", dataset_name: str = "robotics_dataset"):
self.output_dir = output_dir
self.dataset_name = dataset_name
self.world = World(stage_units_in_meters=1.0)
# Create output directories
self.data_dirs = {
'images': os.path.join(output_dir, 'images'),
'labels': os.path.join(output_dir, 'labels'),
'depth': os.path.join(output_dir, 'depth'),
'semantic': os.path.join(output_dir, 'semantic'),
'instances': os.path.join(output_dir, 'instances'),
'metadata': os.path.join(output_dir, 'metadata'),
'lidar': os.path.join(output_dir, 'lidar'),
'camera_params': os.path.join(output_dir, 'camera_params')
}
for dir_path in self.data_dirs.values():
os.makedirs(dir_path, exist_ok=True)
# Initialize components
self.cameras = []
self.lidars = []
self.objects = []
self.lighting_configs = []
self.material_configs = []
# Statistics tracking
self.stats = {
'total_samples': 0,
'generation_time': 0.0,
'data_types': {},
'object_counts': {}
}
self.get_logger().info(f"Synthetic Data Generator initialized for dataset: {dataset_name}")
def setup_scene(self, scene_config: Dict):
"""Setup the scene based on configuration"""
# Add ground plane
self.world.scene.add_default_ground_plane()
# Setup lighting
self.setup_lighting(scene_config.get('lighting', {}))
# Add objects
self.add_objects(scene_config.get('objects', []))
# Setup sensors
self.setup_sensors(scene_config.get('sensors', []))
def setup_lighting(self, lighting_config: Dict):
"""Setup lighting with randomization"""
# Create dome light
dome_light_path = "/World/DomeLight"
omni.kit.commands.execute(
"CreateDomeLightCommand",
path=dome_light_path,
create_xform=True
)
dome_light = get_prim_at_path(dome_light_path)
if dome_light.IsValid():
# Randomize dome light color and intensity
base_color = lighting_config.get('dome_color', (0.2, 0.2, 0.2))
intensity_range = lighting_config.get('dome_intensity_range', (500, 3000))
dome_light.GetAttribute("inputs:color").Set(base_color)
intensity = random.uniform(*intensity_range)
dome_light.GetAttribute("inputs:intensity").Set(intensity)
# Add directional lights with randomization
num_directional_lights = lighting_config.get('num_directional_lights', 1)
for i in range(num_directional_lights):
light_path = f"/World/DirectionalLight_{i}"
omni.kit.commands.execute(
"CreateLightCommand",
path=light_path,
light_type="DistantLight"
)
light = get_prim_at_path(light_path)
if light.IsValid():
# Randomize light properties
color = self.randomize_color(lighting_config.get('light_color_range', [(0.8, 0.8, 0.8), (1.0, 1.0, 1.0)]))
intensity = random.uniform(*lighting_config.get('light_intensity_range', (1000, 5000))
direction = self.randomize_direction()
light.GetAttribute("inputs:color").Set(color)
light.GetAttribute("inputs:intensity").Set(intensity)
light.GetAttribute("xformOp:rotateXYZ").Set(direction)
def add_objects(self, object_configs: List[Dict]):
"""Add objects to the scene with randomization"""
for config in object_configs:
obj_type = config.get('type', 'cube')
obj_name = config.get('name', f"object_{len(self.objects)}")
obj_path = f"/World/{obj_name}"
# Create object based on type
if obj_type == 'cube':
obj_prim = UsdGeom.Cube.Define(self.stage, obj_path)
obj_prim.GetSizeAttr().Set(1.0)
elif obj_type == 'sphere':
obj_prim = UsdGeom.Sphere.Define(self.stage, obj_path)
obj_prim.GetRadiusAttr().Set(0.5)
elif obj_type == 'cylinder':
obj_prim = UsdGeom.Cylinder.Define(self.stage, obj_path)
obj_prim.GetRadiusAttr().Set(0.3)
obj_prim.GetHeightAttr().Set(1.0)
else:
# Default to cube
obj_prim = UsdGeom.Cube.Define(self.stage, obj_path)
obj_prim.GetSizeAttr().Set(1.0)
# Randomize position
pos_range = config.get('position_range', [(-5, -5, 0), (5, 5, 2)])
pos = [
random.uniform(pos_range[0][0], pos_range[1][0]),
random.uniform(pos_range[0][1], pos_range[1][1]),
random.uniform(pos_range[0][2], pos_range[1][2])
]
xform = UsdGeom.Xformable(obj_prim)
xform.AddTranslateOp().Set(Gf.Vec3f(*pos))
# Randomize scale
scale_range = config.get('scale_range', (0.5, 2.0))
scale_val = random.uniform(*scale_range)
xform.AddScaleOp().Set(Gf.Vec3f(scale_val, scale_val, scale_val))
# Apply random material
self.apply_random_material(obj_path, config.get('material_config', {}))
# Add to objects list
self.objects.append({
'path': obj_path,
'type': obj_type,
'config': config
})
def setup_sensors(self, sensor_configs: List[Dict]):
"""Setup sensors for data collection"""
for config in sensor_configs:
sensor_type = config.get('type', 'camera')
sensor_name = config.get('name', f"sensor_{len(self.cameras) + len(self.lidars)}")
if sensor_type == 'camera':
camera = self.setup_camera(sensor_name, config)
self.cameras.append(camera)
elif sensor_type == 'lidar':
lidar = self.setup_lidar(sensor_name, config)
self.lidars.append(lidar)
def setup_camera(self, name: str, config: Dict):
"""Setup a camera sensor"""
camera_path = f"/World/Sensors/{name}"
# Create camera prim
camera_prim = UsdGeom.Camera.Define(self.stage, camera_path)
# Set camera properties
camera_prim.GetFocalLengthAttr().Set(config.get('focal_length', 24.0))
camera_prim.GetHorizontalApertureAttr().Set(config.get('horizontal_aperture', 36.0))
camera_prim.GetVerticalApertureAttr().Set(config.get('vertical_aperture', 24.0))
camera_prim.GetClippingRangeAttr().Set(config.get('clipping_range', (0.1, 1000.0)))
# Set camera transform
position = config.get('position', (0, 0, 2))
rotation = config.get('rotation', (0, 0, 0))
xform = UsdGeom.Xformable(camera_prim)
xform.AddTranslateOp().Set(Gf.Vec3f(*position))
xform.AddRotateXYZOp().Set(Gf.Vec3f(*rotation))
# Create Isaac Sim camera
camera = Camera(
prim_path=camera_path,
frequency=config.get('frequency', 30),
resolution=config.get('resolution', (640, 480))
)
return {
'camera': camera,
'name': name,
'config': config
}
def setup_lidar(self, name: str, config: Dict):
"""Setup a LiDAR sensor"""
lidar_path = f"/World/Sensors/{name}"
lidar = LidarRtx(
prim_path=lidar_path,
position=config.get('position', (0, 0, 1)),
orientation=config.get('orientation', (0, 0, 0, 1)),
config=config.get('lidar_config', "Solid-State-Mixed"),
min_range=config.get('min_range', 0.1),
max_range=config.get('max_range', 25.0),
fov=config.get('fov', 360)
)
return {
'lidar': lidar,
'name': name,
'config': config
}
def generate_dataset(self, num_samples: int, generation_config: Dict):
"""Generate a complete dataset"""
start_time = carb.events.acquire_application().get_current_time()
self.get_logger().info(f"Starting dataset generation: {num_samples} samples")
for i in range(num_samples):
# Randomize scene
self.randomize_scene()
# Generate sample
sample_data = self.generate_sample(f"sample_{i:06d}")
# Save sample
self.save_sample(sample_data)
# Update statistics
self.stats['total_samples'] += 1
# Log progress
if (i + 1) % 100 == 0:
self.get_logger().info(f"Generated {i + 1}/{num_samples} samples")
# Calculate generation time
end_time = carb.events.acquire_application().get_current_time()
self.stats['generation_time'] = end_time - start_time
self.get_logger().info(f"Dataset generation completed: {num_samples} samples in {self.stats['generation_time']:.2f}s")
# Save dataset metadata
self.save_dataset_metadata()
return self.stats
def randomize_scene(self):
"""Randomize scene elements for domain randomization"""
# Randomize lighting
self.randomize_lighting()
# Randomize object positions
self.randomize_object_positions()
# Randomize materials
self.randomize_materials()
# Randomize camera positions (if enabled)
self.randomize_camera_positions()
def randomize_lighting(self):
"""Randomize lighting conditions"""
# This would modify existing lights with random parameters
pass
def randomize_object_positions(self):
"""Randomize object positions"""
for obj in self.objects:
obj_prim = get_prim_at_path(obj['path'])
if obj_prim:
# Get current transform
xform = UsdGeom.Xformable(obj_prim)
# Calculate new random position
pos_range = obj['config'].get('position_range', [(-5, -5, 0), (5, 5, 2)])
new_pos = [
random.uniform(pos_range[0][0], pos_range[1][0]),
random.uniform(pos_range[0][1], pos_range[1][1]),
random.uniform(pos_range[0][2], pos_range[1][2])
]
# Apply new position
xform_op = xform.GetOrderedXformOps()[0] # Assuming first op is translate
xform_op.Set(Gf.Vec3f(*new_pos))
def randomize_materials(self):
"""Randomize object materials"""
for obj in self.objects:
self.apply_random_material(obj['path'], obj['config'].get('material_config', {}))
def generate_sample(self, sample_id: str) -> Dict:
"""Generate a single data sample"""
# Step the simulation to update all sensors
self.world.step(render=True)
sample_data = {
'id': sample_id,
'timestamp': carb.events.acquire_application().get_current_time(),
'camera_data': {},
'lidar_data': {},
'object_poses': {},
'scene_config': self.get_scene_config(),
'metadata': {}
}
# Capture camera data
for cam_info in self.cameras:
camera = cam_info['camera']
cam_name = cam_info['name']
# Get RGB image
rgb_image = camera.get_rgb()
if rgb_image is not None:
sample_data['camera_data'][cam_name] = {
'rgb': rgb_image,
'depth': camera.get_depth(),
'semantic': camera.get_semantic_segmentation(),
'instance': camera.get_instance_segmentation(),
'camera_params': camera.get_intrinsics()
}
# Capture LiDAR data
for lidar_info in self.lidars:
lidar = lidar_info['lidar']
lidar_name = lidar_info['name']
point_cloud = lidar.get_point_cloud()
if point_cloud is not None:
sample_data['lidar_data'][lidar_name] = {
'point_cloud': point_cloud,
'intensities': lidar.get_intensities()
}
# Capture object poses
for obj in self.objects:
# Get object pose from simulation
sample_data['object_poses'][obj['path']] = self.get_object_pose(obj['path'])
return sample_data
def save_sample(self, sample_data: Dict):
"""Save a data sample to disk"""
sample_id = sample_data['id']
# Save camera data
for cam_name, cam_data in sample_data['camera_data'].items():
# Save RGB image
if 'rgb' in cam_data and cam_data['rgb'] is not None:
rgb_path = os.path.join(self.data_dirs['images'], f"{sample_id}_{cam_name}.png")
Image.fromarray(cam_data['rgb']).save(rgb_path)
# Save depth image
if 'depth' in cam_data and cam_data['depth'] is not None:
depth_path = os.path.join(self.data_dirs['depth'], f"{sample_id}_{cam_name}_depth.png")
depth_normalized = ((cam_data['depth'] - cam_data['depth'].min()) /
(cam_data['depth'].max() - cam_data['depth'].min()) * 255).astype(np.uint8)
Image.fromarray(depth_normalized).save(depth_path)
# Save semantic segmentation
if 'semantic' in cam_data and cam_data['semantic'] is not None:
semantic_path = os.path.join(self.data_dirs['semantic'], f"{sample_id}_{cam_name}_semantic.png")
Image.fromarray(cam_data['semantic']).save(semantic_path)
# Save instance segmentation
if 'instance' in cam_data and cam_data['instance'] is not None:
instance_path = os.path.join(self.data_dirs['instances'], f"{sample_id}_{cam_name}_instance.png")
Image.fromarray(cam_data['instance']).save(instance_path)
# Save camera parameters
if 'camera_params' in cam_data:
params_path = os.path.join(self.data_dirs['camera_params'], f"{sample_id}_{cam_name}_params.json")
with open(params_path, 'w') as f:
json.dump(cam_data['camera_params'], f)
# Save LiDAR data
for lidar_name, lidar_data in sample_data['lidar_data'].items():
if 'point_cloud' in lidar_data:
# Save point cloud as numpy array
pc_path = os.path.join(self.data_dirs['lidar'], f"{sample_id}_{lidar_name}_pc.npy")
np.save(pc_path, lidar_data['point_cloud'])
# Save metadata
metadata_path = os.path.join(self.data_dirs['metadata'], f"{sample_id}_metadata.json")
with open(metadata_path, 'w') as f:
json.dump({
'id': sample_data['id'],
'timestamp': sample_data['timestamp'],
'scene_config': sample_data['scene_config'],
'object_poses': sample_data['object_poses']
}, f, indent=2)
def save_dataset_metadata(self):
"""Save overall dataset metadata"""
metadata = {
'dataset_name': self.dataset_name,
'total_samples': self.stats['total_samples'],
'generation_time': self.stats['generation_time'],
'generation_config': {},
'object_distribution': self.get_object_distribution(),
'data_types': list(self.stats['data_types'].keys()),
'date_created': carb.events.acquire_application().get_current_time()
}
metadata_path = os.path.join(self.output_dir, 'dataset_metadata.json')
with open(metadata_path, 'w') as f:
json.dump(metadata, f, indent=2)
def get_object_distribution(self) -> Dict:
"""Get distribution of objects in the dataset"""
distribution = {}
for obj in self.objects:
obj_type = obj['type']
distribution[obj_type] = distribution.get(obj_type, 0) + 1
return distribution
def get_object_pose(self, obj_path: str) -> Dict:
"""Get object pose from simulation"""
# This would interface with Isaac Sim to get actual pose
return {'position': [0, 0, 0], 'orientation': [0, 0, 0, 1]}
def get_scene_config(self) -> Dict:
"""Get current scene configuration"""
return {
'objects': [obj['config'] for obj in self.objects],
'cameras': [cam['config'] for cam in self.cameras],
'lidars': [lidar['config'] for lidar in self.lidars]
}
def apply_random_material(self, prim_path: str, material_config: Dict):
"""Apply a random material to a prim"""
# This would create and apply a random material based on config
pass
def randomize_color(self, color_range: List[Tuple[float, float, float]]) -> Tuple[float, float, float]:
"""Randomize color within range"""
min_color, max_color = color_range
return tuple(
random.uniform(min_color[i], max_color[i]) for i in range(3)
)
def randomize_direction(self) -> Tuple[float, float, float]:
"""Randomize direction/rotation"""
return (
random.uniform(-180, 180),
random.uniform(-90, 90),
random.uniform(-180, 180)
)
def get_logger(self):
"""Get logger instance"""
return carb.Logger()
def main():
"""Main function to demonstrate synthetic data generation"""
# Create data generator
generator = SyntheticDataGenerator(
output_dir="humanoid_robot_dataset",
dataset_name="Humanoid_Perception_Dataset"
)
# Define scene configuration
scene_config = {
'lighting': {
'num_directional_lights': 2,
'dome_color': (0.2, 0.2, 0.2),
'dome_intensity_range': (500, 3000),
'light_color_range': [(0.8, 0.8, 0.8), (1.0, 1.0, 1.0)],
'light_intensity_range': (1000, 5000)
},
'objects': [
{
'type': 'cube',
'name': 'obstacle_1',
'position_range': [(-3, -3, 0), (3, 3, 1)],
'scale_range': (0.3, 1.0)
},
{
'type': 'sphere',
'name': 'target_1',
'position_range': [(-2, -2, 0.5), (2, 2, 1.5)],
'scale_range': (0.2, 0.5)
}
],
'sensors': [
{
'type': 'camera',
'name': 'rgb_camera',
'position': (0, 0, 1.5),
'resolution': (640, 480),
'frequency': 30
},
{
'type': 'lidar',
'name': 'front_lidar',
'position': (0, 0, 1.0),
'lidar_config': 'Solid-State-Mixed',
'min_range': 0.1,
'max_range': 25.0
}
]
}
# Setup scene
generator.setup_scene(scene_config)
# Define generation configuration
generation_config = {
'num_samples': 1000,
'domain_randomization': True,
'annotation_types': ['rgb', 'depth', 'semantic', 'instance'],
'data_augmentation': True
}
# Generate dataset
stats = generator.generate_dataset(1000, generation_config)
print(f"Dataset generation completed with stats: {stats}")
if __name__ == '__main__':
main()