Skip to content

Transform

drone_controllers.transform

Transformations between physical parameters of the quadrotors.

Conversions such as from motor forces to rotor speeds, or from thrust to PWM, are bundled in this module.

motor_force2rotor_vel(motor_forces, kf)

Convert motor forces to rotor velocities.

Parameters:

Name Type Description Default
motor_forces Array

Motor forces in SI units with shape (..., N).

required
kf float | Array

Thrust coefficient.

required

Returns:

Type Description
Array

Array of rotor velocities in rad/s with shape (..., N).

rotor_vel2body_force(rotor_vel, kf)

Convert rotor velocities to motor forces.

rotor_vel2body_torque(rotor_vel, kf, km, L, mixing_matrix)

Convert rotor velocities to motor torques.

force2pwm(thrust, thrust_max, pwm_max)

Convert thrust in N to thrust in PWM.

Parameters:

Name Type Description Default
thrust Array | float

Array or float of the thrust in [N]

required
thrust_max Array | float

Maximum thrust in [N]

required
pwm_max Array | float

Maximum PWM value

required

Returns:

Type Description
Array

Thrust converted in PWM.

pwm2force(pwm, thrust_max, pwm_max)

Convert pwm thrust command to actual thrust.

Parameters:

Name Type Description Default
pwm Array | float

Array or float of the pwm value

required
thrust_max Array | float

Maximum thrust in [N]

required
pwm_max Array | float

Maximum PWM value

required

Returns:

Name Type Description
thrust Array | float

Array or float thrust in [N]

The transform module provides utility functions for converting between different physical representations of quadrotor parameters.

Motor Force Conversions

motor_force2rotor_vel

Convert motor forces to rotor velocities using the thrust coefficient:

import numpy as np
from drone_controllers.transform import motor_force2rotor_vel

motor_forces = np.array([0.1, 0.1, 0.1, 0.1])  # N
kf = 3.16e-10  # Thrust coefficient
rotor_speeds = motor_force2rotor_vel(motor_forces, kf)
print(f"Rotor speeds: {rotor_speeds} rad/s")

rotor_vel2body_force

Convert rotor velocities to body forces:

from drone_controllers.transform import rotor_vel2body_force

rotor_speeds = np.array([1000, 1000, 1000, 1000])  # rad/s
kf = 3.16e-10
body_force = rotor_vel2body_force(rotor_speeds, kf)  # Returns [0, 0, total_thrust]

rotor_vel2body_torque

Convert rotor velocities to body torques using mixing matrix:

from drone_controllers.transform import rotor_vel2body_torque

rotor_speeds = np.array([1000, 1000, 1000, 1000])  # rad/s
kf = 3.16e-10
km = 7.94e-12
L = 0.046  # Arm length in meters
mixing_matrix = np.array([[1, -1, 1], [-1, 1, 1], [1, 1, -1], [-1, -1, -1]])

body_torque = rotor_vel2body_torque(rotor_speeds, kf, km, L, mixing_matrix)

PWM Conversions

force2pwm

Convert thrust forces to PWM values:

from drone_controllers.transform import force2pwm

thrust = 0.4  # N
thrust_max = 0.6  # N
pwm_max = 65535

pwm_value = force2pwm(thrust, thrust_max, pwm_max)
print(f"PWM value: {pwm_value}")

pwm2force

Convert PWM values back to thrust forces:

from drone_controllers.transform import pwm2force

pwm_value = 43690
thrust_max = 0.6  # N  
pwm_max = 65535

thrust = pwm2force(pwm_value, thrust_max, pwm_max)
print(f"Thrust: {thrust} N")

Array API Compatibility

All transform functions work with the array API standard and support broadcasting:

import jax.numpy as jnp
from drone_controllers.transform import motor_force2rotor_vel

# Works with JAX arrays
motor_forces_jax = jnp.array([[0.1, 0.1, 0.1, 0.1], 
                              [0.2, 0.2, 0.2, 0.2]])  # Batch of 2
kf = 3.16e-10

rotor_speeds = motor_force2rotor_vel(motor_forces_jax, kf)
print(f"Batch output shape: {rotor_speeds.shape}")  # (2, 4)

Usage in Controller Pipeline

These transforms are typically used at the end of the control pipeline:

from drone_controllers import parametrize
from drone_controllers.mellinger import state2attitude, attitude2force_torque, force_torque2rotor_vel

# Set up controller pipeline
state_ctrl = parametrize(state2attitude, "cf2x_L250")
attitude_ctrl = parametrize(attitude2force_torque, "cf2x_L250")
rotor_ctrl = parametrize(force_torque2rotor_vel, "cf2x_L250")

# Run full pipeline
rpyt, _ = state_ctrl(pos, quat, vel, ang_vel, cmd)
force, torque, _ = attitude_ctrl(pos, quat, vel, ang_vel, rpyt)
rotor_speeds = rotor_ctrl(force, torque)

# Convert to actual motor forces if needed
from drone_controllers.transform import motor_force2rotor_vel
# The rotor_ctrl already returns rotor speeds, but if you had forces:
# rotor_speeds = motor_force2rotor_vel(motor_forces, kf=3.16e-10)

The transform module provides utilities for coordinate transformations and rotation representations commonly used in drone control.

Rotation Conversions

Rotation Matrices

Functions for working with 3x3 rotation matrices:

from drone_controllers.transform import (
    rotation_matrix_x, rotation_matrix_y, rotation_matrix_z,
    matrix_to_euler, euler_to_matrix
)
import numpy as np

# Elementary rotations
R_x = rotation_matrix_x(np.pi/4)  # 45° about x-axis
R_y = rotation_matrix_y(np.pi/6)  # 30° about y-axis  
R_z = rotation_matrix_z(np.pi/3)  # 60° about z-axis

# Combined rotation
R_combined = R_z @ R_y @ R_x

Euler Angles

Convert between rotation matrices and Euler angles:

# Convert rotation matrix to Euler angles (ZYX convention)
roll, pitch, yaw = matrix_to_euler(rotation_matrix)

# Convert Euler angles to rotation matrix
R = euler_to_matrix(roll, pitch, yaw)

Quaternions

Quaternion operations for attitude representation:

from drone_controllers.transform import (
    quaternion_to_matrix, matrix_to_quaternion,
    quaternion_multiply, quaternion_conjugate
)

# Convert between quaternions and rotation matrices
quat = matrix_to_quaternion(rotation_matrix)
R = quaternion_to_matrix(quat)

# Quaternion operations
q1 = np.array([0.7071, 0, 0, 0.7071])  # 90° about z-axis
q2 = np.array([0.7071, 0.7071, 0, 0])  # 90° about x-axis
q_combined = quaternion_multiply(q1, q2)

Vector Operations

Cross Product Matrix

Generate skew-symmetric matrices for cross products:

from drone_controllers.transform import skew_symmetric

vector = np.array([1, 2, 3])
skew_matrix = skew_symmetric(vector)

# Now: skew_matrix @ other_vector == np.cross(vector, other_vector)

Vector Projections

Project vectors onto planes or other vectors:

from drone_controllers.transform import project_vector_onto_plane

vector = np.array([1, 1, 1])
plane_normal = np.array([0, 0, 1])  # XY plane
projected = project_vector_onto_plane(vector, plane_normal)

Coordinate Frame Transformations

Frame Conversions

Transform vectors between different coordinate frames:

from drone_controllers.transform import transform_vector

# Transform vector from body to world frame
vector_body = np.array([1, 0, 0])  # Forward in body frame
rotation_body_to_world = euler_to_matrix(0, np.pi/4, 0)  # 45° pitch
vector_world = transform_vector(vector_body, rotation_body_to_world)

Common Frames

Utilities for standard aerospace coordinate frames:

from drone_controllers.transform import (
    ned_to_enu, enu_to_ned,
    body_to_world, world_to_body
)

# Convert between NED and ENU conventions
position_ned = np.array([10, 5, -2])  # North, East, Down
position_enu = ned_to_enu(position_ned)  # East, North, Up

Angular Velocity Operations

Integration on SO(3)

Integrate angular velocities on the rotation group:

from drone_controllers.transform import integrate_angular_velocity

current_attitude = np.eye(3)
angular_velocity = np.array([0.1, 0.2, 0.05])  # rad/s
dt = 0.01

new_attitude = integrate_angular_velocity(
    current_attitude, angular_velocity, dt
)

Rodrigues Formula

Direct integration using Rodrigues' rotation formula:

from drone_controllers.transform import rodrigues_rotation

axis = np.array([0, 0, 1])  # Rotation axis
angle = np.pi/2  # 90 degrees
rotation_matrix = rodrigues_rotation(axis, angle)

Utility Functions

Angle Normalization

Normalize angles to standard ranges:

from drone_controllers.transform import normalize_angle, wrap_to_pi

angle = 3 * np.pi  # Large angle
normalized = normalize_angle(angle)  # Wrapped to [0, 2π)
wrapped = wrap_to_pi(angle)  # Wrapped to [-π, π]

Rotation Composition

Compose multiple rotations efficiently:

from drone_controllers.transform import compose_rotations

rotations = [R1, R2, R3]  # List of rotation matrices
composed = compose_rotations(rotations)  # Equivalent to R3 @ R2 @ R1

Constants and Conventions

The module defines standard constants and conventions:

from drone_controllers.transform import (
    GRAVITY_EARTH,  # Standard gravity (9.80665 m/s²)
    IDENTITY_ROTATION,  # 3x3 identity matrix
    ZERO_VECTOR,  # Zero 3D vector
)

Performance Notes

  • Rotation matrices are preferred for computational efficiency
  • Quaternions are used when interpolation is needed
  • Euler angles are primarily for human-readable output
  • All functions support vectorized operations where possible