12. Data Logging

12.1. Learning Objectives

This tutorial shows how to record data using the DataLogger and play it back in Omniverse Isaac Sim. After this tutorial, you can record and play back states and actions in your pipeline while using Omniverse Isaac Sim.

10-15 Minute Tutorial

12.2. Getting Started

Prerequisites

  • Review the Required Tutorial series prior to beginning this tutorial.

12.3. Recording Data

Let’s begin by using the following target extension example to record data

  • Open the Follow Target example Isaac Examples > Manipulation > Follow Target.

  • Click on LOAD under the World Controls to load Franka with a visual target cube.

  • Choose the output directory of the json file to save the data under Data Logging in the Follow Target menu.

  • Click on Follow Target under Task Controls.

  • Click on START LOGGING

  • Move the visual cube around so that Franka follows it using the RMPFlowController

  • After a few seconds, click on SAVE DATA

  • Click on File > New From Stage Template > Empty to create new stage.

  • The data should be recorded under the chosen file shown in the Output Directory text field.

12.3.1. Code Overview

Open the extension example code located at ~/.local/share/ov/pkg/isaac_sim-2021.2.1/extension_examples/follow_target/follow_target.py using the open-source code button located at the top of the menu.

First, let’s look at the logging function.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def _on_start_logging_event(self):
    world = self.get_world()
    data_logger = world.get_data_logger() # a DataLogger object is defined in the World by default
    robot_name = self._task_params["robot_name"]["value"]
    target_name = self._task_params["target_name"]["value"]

    # A data logging function is called at every time step index if the data logger is started already.
    # We define the function here. The tasks and scene are passed to this function when called.
    def frame_logging_func(tasks, scene):
        # return always a dict
        return {
            "joint_positions": scene.get_object(robot_name).get_joint_positions().tolist(), # save data as lists since its a json file.
            "applied_joint_positions": scene.get_object(robot_name).get_applied_action().joint_positions.tolist(),
            "target_position": scene.get_object(target_name).get_world_pose()[0].tolist(),
        }

    data_logger.add_data_frame_logging_func(frame_logging_func) # adds the function to be called at each physics time step.
    data_logger.start() # starts the data logging
    return

Now let’s look at how to save the data collected so far.

1
2
3
4
5
6
def _on_save_data_event(self, log_path):
    world = self.get_world()
    data_logger = world.get_data_logger() # a DataLogger object is defined in the World by default
    data_logger.save(log_path=log_path) # Saves the collected data to the json file specified.
    data_logger.reset() # Resets the DataLogger internal state so that another set of data can be collected and saved separately.
    return

12.3.2. Inspect the Data

As shown below, the DataLogger takes care of logging the time in seconds and time step corresponding to each data frame.

1
{"Isaac Sim Data": [{"current_time": 1.4833334106951952, "current_time_step": 89, "data": {"joint_positions": [0.07561380416154861, -1.2318825721740723, 0.11344202607870102, -2.4259397983551025, 0.0970514565706253, 1.6226640939712524, 0.8470714688301086, 4.0, 3.997776985168457], "applied_joint_positions": [0.07291083037853241, -1.2202218770980835, 0.1190749853849411, -2.39223575592041, 0.11230156570672989, 1.3975754976272583, 0.9029524326324463, 4.0, 4.0], "target_position": [0.0, 10.0, 70.0]}}, {"current_time": 1.5000000782310963, "current_time_step": 90, "data": {"joint_positions": [0.07484950870275497, -1.2287049293518066, 0.11509127914905548, -2.416816234588623, 0.09880664199590683, 1.603981614112854, 0.8490884304046631, 4.0, 3.997793197631836], "applied_joint_positions": [0.07221028953790665, -1.2172484397888184, 0.1205318346619606, -2.3833296298980713, 0.11395926028490067, 1.3804354667663574, 0.9063650369644165, 4.0, 4.0], "target_position": [0.0, 10.0, 70.0]}}, {"current_time": 1.5166667457669973, "current_time_step": 91, "data": {"joint_positions": [0.07410304993391037, -1.2255841493606567, 0.11668683588504791, -2.4077510833740234, 0.10055229812860489, 1.58543062210083, 0.8511277437210083, 4.0, 3.997816562652588], "applied_joint_positions": [0.07153353840112686, -1.2144113779067993, 0.1219213530421257, -2.3745198249816895, 0.11559101194143295, 1.363703727722168, 0.9096996784210205, 4.0, 4.0], "target_position": [0.0, 10.0, 70.0]}] }

12.4. Replaying Back Data

Similarly, we will use another extension example provided under Isaac Examples to play back the recorded data.

  • Open the Follow Target example Isaac Examples > Manipulation > Replay Follow Target.

  • Click on LOAD under the World Controls to load Franka with a visual target cube.

  • Point to the json file saved in the last step in Recording Data section under Data File at the Data Replay section in the menu.

  • Click on Replay Trajectory under Data Replay to replay the actions only and wait for the trajectory to finish replaying.

  • Click on Reset

  • Similarly, click on Replay Scene under Data Replay to replay the actions and the cube position.

  • Click on File > New From Stage Template > Empty to create new stage.

12.4.1. Code Overview

Open the extension example code located at ~/.local/share/ov/pkg/isaac_sim-2021.2.1/extension_examples/follow_target/follow_target.py using the open-source code button located at the top of the menu.

First, let’s look at how to load the data and replay the trajectory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
async def _on_replay_trajectory_event_async(self, data_file):
    # Loads the data from the json file
    self._data_logger.load(log_path=data_file)
    world = self.get_world()
    await world.play_async()
    # Adds the physics callback to set the joint targets every frame
    world.add_physics_callback("replay_trajectory", self._on_replay_trajectory_step)
    return

def _on_replay_trajectory_step(self, step_size):
    if self._world.current_time_step_index < self._data_logger.get_num_of_data_frames():
        # To sync time steps and get the data frame at the same time step
        data_frame = self._data_logger.get_data_frame(data_frame_index=self._world.current_time_step_index)
        # Applies the same recorded action to the articulation controller
        self._articulation_controller.apply_action(
            ArticulationAction(joint_positions=data_frame.data["applied_joint_positions"])
        )
    return

Now let’s look at how to replay the scene, including the goal cube position.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def _on_replay_scene_step(self, step_size):
    if self._world.current_time_step_index < self._data_logger.get_num_of_data_frames():
        target_name = self._task_params["target_name"]["value"]
        data_frame = self._data_logger.get_data_frame(data_frame_index=self._world.current_time_step_index)
        self._articulation_controller.apply_action(
            ArticulationAction(joint_positions=data_frame.data["applied_joint_positions"])
        )
        # Sets the world position of the goal cube to the same recoded position
        self._world.scene.get_object(target_name).set_world_pose(
            position=np.array(data_frame.data["target_position"])
        )
    return

12.5. Summary

This tutorial covered the following topics:

  1. Using the DataLogger to save data.

  2. Using the DataLogger to replay data.