Simulation with Moving People

In this tutorial, you will learn how to add moving people to your standalone simulations. This is useful for dynamic applications such as target tracking, surveillance, and search and rescue missions.

Simulating drones and people using Pegasus

Note

Here we follow a different approach to simulate moving people than what is provided in the official NVIDIA Isaac Sim documentation. We believe that our API is simpler and more intuitive to use in most user cases. I also needed this for my own Ph.D. research :)

0. Preparation

This tutorial assumes that you have followed the Your First Simulation section first.

1. Code

The tutorial corresponds to the 9_people.py example in the examples directory.

  1#!/usr/bin/env python
  2"""
  3| File: 9_people.py
  4| License: BSD-3-Clause. Copyright (c) 2024, Marcelo Jacinto. All rights reserved.
  5| Description: This files serves as an example on how to build an app that makes use of the Pegasus API to run a simulation
  6| where people move around in the world.
  7"""
  8
  9# Imports to start Isaac Sim from this script
 10import carb
 11from isaacsim import SimulationApp
 12
 13# Start Isaac Sim's simulation environment
 14# Note: this simulation app must be instantiated right after the SimulationApp import, otherwise the simulator will crash
 15# as this is the object that will load all the extensions and load the actual simulator.
 16simulation_app = SimulationApp({"headless": False})
 17
 18# -----------------------------------
 19# The actual script should start here
 20# -----------------------------------
 21import omni.timeline
 22from omni.isaac.core.world import World
 23from omni.isaac.core.utils.extensions import disable_extension, enable_extension
 24
 25EXTENSIONS_PEOPLE = [
 26    'omni.anim.people', 
 27    'omni.anim.navigation.bundle', 
 28    'omni.anim.timeline',
 29    'omni.anim.graph.bundle', 
 30    'omni.anim.graph.core', 
 31    'omni.anim.graph.ui',
 32    'omni.anim.retarget.bundle', 
 33    'omni.anim.retarget.core',
 34    'omni.anim.retarget.ui', 
 35    'omni.kit.scripting',
 36    'omni.graph.io',
 37    'omni.anim.curve.core',
 38]
 39
 40for ext_people in EXTENSIONS_PEOPLE:
 41    enable_extension(ext_people)
 42
 43# Enable/disable ROS bridge extensions to keep only ROS2 Bridge
 44disable_extension("omni.isaac.ros_bridge")
 45enable_extension("omni.isaac.ros2_bridge")
 46
 47# Update the simulation app with the new extensions
 48simulation_app.update()
 49
 50# -------------------------------------------------------------------------------------------------
 51# These lines are needed to restart the USD stage and make sure that the people extension is loaded
 52# -------------------------------------------------------------------------------------------------
 53import omni.usd
 54omni.usd.get_context().new_stage()
 55
 56import numpy as np
 57
 58# Import the Pegasus API for simulating drones
 59from pegasus.simulator.params import ROBOTS, SIMULATION_ENVIRONMENTS
 60from pegasus.simulator.logic.interface.pegasus_interface import PegasusInterface
 61from pegasus.simulator.logic.people.person import Person
 62from pegasus.simulator.logic.people.person_controller import PersonController
 63from pegasus.simulator.logic.graphical_sensors.monocular_camera import MonocularCamera
 64from pegasus.simulator.logic.backends.px4_mavlink_backend import PX4MavlinkBackend, PX4MavlinkBackendConfig
 65from pegasus.simulator.logic.backends.ros2_backend import ROS2Backend
 66from pegasus.simulator.logic.vehicles.multirotor import Multirotor, MultirotorConfig
 67from pegasus.simulator.logic.interface.pegasus_interface import PegasusInterface
 68
 69# Example controller class that make a person move in a circle around the origin of the world
 70# Note: You could create a different controller with a different behaviour. For instance, you could:
 71# 1. read the keyboard input to move the person around the world.
 72# 2. read the target position from a ros topic,
 73# 3. read the target position from a file,
 74# 4. etc.
 75class CirclePersonController(PersonController):
 76
 77    def __init__(self):
 78        super().__init__()
 79
 80        self._radius = 5.0
 81        self.gamma = 0.0
 82        self.gamma_dot = 0.3
 83        
 84    def update(self, dt: float):
 85
 86        # Update the reference position for the person to track
 87        self.gamma += self.gamma_dot * dt
 88        
 89        # Set the target position for the person to track
 90        self._person.update_target_position([self._radius * np.cos(self.gamma), self._radius * np.sin(self.gamma), 0.0])
 91        
 92
 93# Auxiliary scipy and numpy modules
 94from scipy.spatial.transform import Rotation
 95
 96# -------------------------------------------------------------------------------------------------
 97# Define the PegasusApp class where the simulation will be run
 98# -------------------------------------------------------------------------------------------------
 99class PegasusApp:
100    """
101    A Template class that serves as an example on how to build a simple Isaac Sim standalone App.
102    """
103
104    def __init__(self):
105        """
106        Method that initializes the PegasusApp and is used to setup the simulation environment.
107        """
108
109        # Acquire the timeline that will be used to start/stop the simulation
110        self.timeline = omni.timeline.get_timeline_interface()
111
112        # Start the Pegasus Interface
113        self.pg = PegasusInterface()
114
115        # Acquire the World, .i.e, the singleton that controls that is a one stop shop for setting up physics,
116        # spawning asset primitives, etc.
117        self.pg._world = World(**self.pg._world_settings)
118        self.world = self.pg.world
119
120        # Launch one of the worlds provided by NVIDIA
121        #self.pg.load_environment(SIMULATION_ENVIRONMENTS["Curved Gridroom"])
122        self.pg.load_asset(SIMULATION_ENVIRONMENTS["Curved Gridroom"], "/World/layout")
123
124        # Check the available assets for people
125        people_assets_list = Person.get_character_asset_list()
126        for person in people_assets_list:
127            print(person)
128
129        # Create the controller to make on person walk around in circles
130        person_controller = CirclePersonController()
131        p1 = Person("person1", "original_male_adult_construction_05", init_pos=[3.0, 0.0, 0.0], init_yaw=1.0, controller=person_controller)
132        
133        # Create a person without setting up a controller, and just setting a manual target position for it to track
134        p2 = Person("person2", "original_female_adult_business_02", init_pos=[2.0, 0.0, 0.0])
135        p2.update_target_position([10.0, 0.0, 0.0], 1.0)
136
137        # Create the vehicle
138        # Try to spawn the selected robot in the world to the specified namespace
139        config_multirotor = MultirotorConfig()
140        # # Create the multirotor configuration
141        mavlink_config = PX4MavlinkBackendConfig({
142            "vehicle_id": 0,
143            "px4_autolaunch": True,
144            "px4_dir": "/home/marcelo/PX4-Autopilot"
145        })
146        config_multirotor.backends = [
147            PX4MavlinkBackend(mavlink_config),
148            ROS2Backend(vehicle_id=1, 
149                config={
150                    "namespace": 'drone', 
151                    "pub_sensors": False,
152                    "pub_graphical_sensors": True,
153                    "pub_state": True,
154                    "pub_tf": False,
155                    "sub_control": False,})]
156
157        # Create a camera
158        config_multirotor.graphical_sensors = [MonocularCamera("camera", config={"update_rate": 60.0})]
159
160        Multirotor(
161            "/World/quadrotor",
162            ROBOTS['Iris'],
163            0,
164            [0.0, 0.0, 0.07],
165            Rotation.from_euler("XYZ", [0.0, 0.0, 0.0], degrees=True).as_quat(),
166            config=config_multirotor,
167        )
168
169        # Set the camera of the viewport to a nice position
170        self.pg.set_viewport_camera([5.0, 9.0, 6.5], [0.0, 0.0, 0.0])
171
172        # Reset the simulation environment so that all articulations (aka robots) are initialized
173        self.world.reset()
174
175        # Auxiliar variable for the timeline callback example
176        self.stop_sim = False
177
178    def run(self):
179        """
180        Method that implements the application main loop, where the physics steps are executed.
181        """
182
183        # Start the simulation
184        self.timeline.play()
185
186        # The "infinite" loop
187        while simulation_app.is_running() and not self.stop_sim:
188            # Update the UI of the app and perform the physics step
189            self.world.step(render=True)
190
191        # Cleanup and stop
192        carb.log_warn("PegasusApp Simulation App is closing.")
193        self.timeline.stop()
194        simulation_app.close()
195
196def main():
197
198    # Instantiate the template app
199    pg_app = PegasusApp()
200
201    # Run the application loop
202    pg_app.run()
203
204if __name__ == "__main__":
205    main()

2. Explanation

To start a pre-programmed simulation with moving people, you need to ensure that the People extension provided by NVIDIA is enabled. Note, in Isaac 4.1.0 we also need to create a new stage for the people extension to start properly.

23from omni.isaac.core.utils.extensions import disable_extension, enable_extension
24
25EXTENSIONS_PEOPLE = [
26    'omni.anim.people', 
27    'omni.anim.navigation.bundle', 
28    'omni.anim.timeline',
29    'omni.anim.graph.bundle', 
30    'omni.anim.graph.core', 
31    'omni.anim.graph.ui',
32    'omni.anim.retarget.bundle', 
33    'omni.anim.retarget.core',
34    'omni.anim.retarget.ui', 
35    'omni.kit.scripting',
36    'omni.graph.io',
37    'omni.anim.curve.core',
38]
39
40for ext_people in EXTENSIONS_PEOPLE:
41    enable_extension(ext_people)
42
43# Enable/disable ROS bridge extensions to keep only ROS2 Bridge
44disable_extension("omni.isaac.ros_bridge")
45enable_extension("omni.isaac.ros2_bridge")
46
47# Update the simulation app with the new extensions
48simulation_app.update()
49# -------------------------------------------------------------------------------------------------
50# These lines are needed to restart the USD stage and make sure that the people extension is loaded
51# -------------------------------------------------------------------------------------------------
52import omni.usd
53omni.usd.get_context().new_stage()

We also need to import the Person and the PersonController classes. This follows the same strategy adopted for the vehicles and the control backends.

61from pegasus.simulator.logic.people.person import Person
62from pegasus.simulator.logic.people.person_controller import PersonController

The PersonController is an interface class that a user defined controller must inherit from. This controller is responsible for defining the behavior of the person in the simulation. In this example, our controller makes a person walk in circles, but you can define any behavior you want. For instance, you could:

  • read the keyboard input to move the person around the world.

  • read the target position from a ros topic,

  • read the target position from a file,

  • etc…

69# Example controller class that make a person move in a circle around the origin of the world
70# Note: You could create a different controller with a different behaviour. For instance, you could:
71# 1. read the keyboard input to move the person around the world.
72# 2. read the target position from a ros topic,
73# 3. read the target position from a file,
74# 4. etc.
75class CirclePersonController(PersonController):
76
77    def __init__(self):
78        super().__init__()
79
80        self._radius = 5.0
81        self.gamma = 0.0
82        self.gamma_dot = 0.3
83        
84    def update(self, dt: float):
85
86        # Update the reference position for the person to track
87        self.gamma += self.gamma_dot * dt
88        
89        # Set the target position for the person to track
90        self._person.update_target_position([self._radius * np.cos(self.gamma), self._radius * np.sin(self.gamma), 0.0])

Note

To check all the functions that you can implement in your controller, check the PersonController class in the API reference.

The next step is to create a person in the simulation. But, you let’s imagine you don’t know which 3D models are available. Well, you can call the static method Person.get_character_asset_list() function to list all the available models.

124        # Check the available assets for people
125        people_assets_list = Person.get_character_asset_list()
126        for person in people_assets_list:
127            print(person)

Now that you know which models you can load, you can create a person in the simulation. You can set the initial position, orientation, and the controller that will define the behavior of the person. Note that if you just want to send a person to a given position manually (without using a controller), you can! Just check “Person 2”.

129        # Create the controller to make on person walk around in circles
130        person_controller = CirclePersonController()
131        p1 = Person("person1", "original_male_adult_construction_05", init_pos=[3.0, 0.0, 0.0], init_yaw=1.0, controller=person_controller)
132        
133        # Create a person without setting up a controller, and just setting a manual target position for it to track
134        p2 = Person("person2", "original_female_adult_business_02", init_pos=[2.0, 0.0, 0.0])
135        p2.update_target_position([10.0, 0.0, 0.0], 1.0)

3. Execution

Now let’s run the Python script:

ISAACSIM_PYTHON examples/9_people.py

This should open a stage with a blue ground-plane with an 3DR Iris vehicle model in it. The simulation should start playing automatically and the stage being rendered. You will see 2 people being simulated with one of them walking in circles.

If you open QGroundControl you can control the vehicle.