from bagpy import bagreader
import pandas as pd
import numpy as np
from scipy.spatial.transform import Rotation as R

# Initialize an empty DataFrame to store the results
global_df = pd.DataFrame()

# Iterate over the different heights
for height in ['ground', '10cm', '30cm']:
    # Path to your ROS bag file (modify based on the height)
    bag_file = f'bags/tof_imu_altitude_nodes_characterization_{height}.bag'
    
    # Read the bag into a pandas dataframe
    b = bagreader(bag_file)

    # Iterate over each topic in the ROS bag
    for topic_name in b.topic_table['Topics']:
        df = pd.read_csv(b.message_by_topic(topic_name))

        if b.topic_table[b.topic_table['Topics'] == topic_name]['Types'].values[0] == 'sensor_msgs/Range':
            # ToF data (distance)
            mean = df['range'].mean()
            std = df['range'].std()
            rsd = (std / np.abs(mean)) * 100 if mean != 0 else np.nan
            
            # Add the result to global_df using pd.concat
            global_df = pd.concat([global_df, pd.DataFrame({
                'Height': [height],
                'Quantity': ['ToF Distance (m)'],
                'Mean': [mean],
                'Standard Deviation': [std],
                'RSD (%)': [rsd]
            })], ignore_index=True)

        if b.topic_table[b.topic_table['Topics'] == topic_name]['Types'].values[0] == 'sensor_msgs/Imu':
            # Compute the mean, standard deviation, and relative standard deviation for IMU data
            for axis in ['x', 'y', 'z']:
                # Linear Acceleration
                mean = df[f'linear_acceleration.{axis}'].mean()
                std = df[f'linear_acceleration.{axis}'].std()
                rsd = (std / np.abs(mean)) * 100 if mean != 0 else np.nan
                
                # Add to global_df using pd.concat
                global_df = pd.concat([global_df, pd.DataFrame({
                    'Height': [height],
                    'Quantity': [f'Linear Acceleration {axis.upper()} (m/s^2)'],
                    'Mean': [mean],
                    'Standard Deviation': [std],
                    'RSD (%)': [rsd]
                })], ignore_index=True)

                # Angular Velocity
                mean = df[f'angular_velocity.{axis}'].mean()
                std = df[f'angular_velocity.{axis}'].std()
                rsd = (std / np.abs(mean)) * 100 if mean != 0 else np.nan
                
                # Add to global_df using pd.concat
                global_df = pd.concat([global_df, pd.DataFrame({
                    'Height': [height],
                    'Quantity': [f'Angular Velocity {axis.upper()} (rad/s)'],
                    'Mean': [mean],
                    'Standard Deviation': [std],
                    'RSD (%)': [rsd]
                })], ignore_index=True)

            # Orientation (Quaternion -> Euler)
            quaternions = df[['orientation.w', 'orientation.x', 'orientation.y', 'orientation.z']].to_numpy()
            rot = R.from_quat(quaternions)
            euler_angles = rot.as_euler('zyx', degrees=True)  # Convert to degrees (roll, pitch, yaw)

            # Add Euler angles to the DataFrame
            df['roll'] = euler_angles[:, 0]
            df['pitch'] = euler_angles[:, 1]
            df['yaw'] = euler_angles[:, 2]

            # Compute the mean, std, and rsd for roll, pitch, yaw
            for axis in ['roll', 'pitch', 'yaw']:
                mean = df[axis].mean()
                std = df[axis].std()
                rsd = (std / np.abs(mean)) * 100 if mean != 0 else np.nan
                
                # Add to global_df using pd.concat
                global_df = pd.concat([global_df, pd.DataFrame({
                    'Height': [height],
                    'Quantity': [f'{axis.capitalize()} (Euler) (degrees)'],
                    'Mean': [mean],
                    'Standard Deviation': [std],
                    'RSD (%)': [rsd]
                })], ignore_index=True)

# Convert the DataFrame to LaTeX format
latex_table = global_df.to_latex(index=False, caption='IMU and ToF Mean and Standard Deviation at Different Heights')

global_df

# # Optionally save the LaTeX table to a file
# with open("imu_tof_stats_table.tex", "w") as f:
#     f.write(latex_table)
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 1
----> 1 from bagpy import bagreader
      2 import pandas as pd
      3 import numpy as np

ModuleNotFoundError: No module named 'bagpy'

Measuring the frequency of topics#

The frequency of the measurements of the IMU and Time of Flight sensors is measured using the rostopic hz command. The following image contains the output of the command:

Measurement of the topics frequency

As visible in the image the frequencies have small standard deviations compared to the average rate.

Optical flow node#

We characterize the optical flow (visual odometry) node. This node takes as input images from the camera and compute the motion vectors that measure the displacements of the same pixels in two consecutive frames. Then these are projected to the ground and the velocity vector of the drone is estimated by averaging them out.

from bagpy import bagreader
import pandas as pd

# Read the bag file
b = bagreader('bags/visual_odometry_pi4.bag')

# Display available topics in the bag
print(b.topic_table)

# Image resolution 80x60
[INFO]  Data folder bags/visual_odometry_pi4 already exists. Not creating.
                                         Topics              Types  \
0  /discovery/optical_flow_node/visual_odometry  nav_msgs/Odometry   

   Message Count  Frequency  
0            569  22.030932  
# Read the messages from the odometry topic into a DataFrame
df = pd.read_csv(b.message_by_topic('/discovery/optical_flow_node/visual_odometry'))

# Compute mean and standard deviation for the linear velocities
for axis in ['x', 'y', 'z']:
    linear_mean = df[f'twist.twist.linear.{axis}'].mean()
    linear_std = df[f'twist.twist.linear.{axis}'].std()

    if axis == 'z':
        assert linear_mean == 0, "[WARNING] Linear velocity in z-axis should be zero!"
    
    print(f"Linear Velocity {axis.upper()}: Mean = {linear_mean:.5f}, Std = {linear_std:.5f}")
Linear Velocity X: Mean = 0.00039, Std = 0.00270
Linear Velocity Y: Mean = 0.00109, Std = 0.01113
Linear Velocity Z: Mean = 0.00000, Std = 0.00000