astrix.spatial package

class Acceleration(vec, time, _xp)[source]

Bases: object

Acceleration vector(s) in ECEF coordinates (ax, ay, az) in m/s². Associated with a Time object for the time instances of the accelerations. Internal use only, typically created from Path objects. No data validation is performed.

Parameters:
  • vec (Array) – Acceleration vectors in ECEF coordinates (ax, ay, az) in m/s². Shape (n, 3).

  • time (Time) – Time object associated with the accelerations. Length must match number of acceleration vectors.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

  • _xp (Any)

classmethod from_data(vec, time, backend=None)[source]

Create an Acceleration object from acceleration vector array and Time object.

Return type:

Acceleration

Parameters:
  • vec (Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • time (Time)

  • backend (str | Any | None)

convert_to(backend)[source]

Convert the Acceleration object to a different backend.

Return type:

Acceleration

Parameters:

backend (str | Any | None)

interp(time, method='linear', check_bounds=True)[source]

Interpolate the Acceleration to the given times using the specified method. Currently only ‘linear’ interpolation is supported.

Return type:

Acceleration

Parameters:
  • time (Time)

  • method (str)

  • check_bounds (bool)

rotate(rot)[source]
Return type:

Acceleration

Parameters:

rot (Rotation)

property backend: str
property magnitude: Any

Get the acceleration magnitude in m/s².

time: Time
property unit: Any

Get the unit acceleration vector.

vec: Any
class Frame(rot, loc=None, ref_frame=None, backend=None, name='unnamed-frame')[source]

Bases: object

A reference frame defined by a rotation and location. Can be static or time-varying, and can have rotation defined relative to another Frame. Combines RotationLike and Location objects, and manages time associations.

Parameters:
  • rot (Rotation | RotationSequence) – A scipy Rotation object (single rotation) or RotationSequence (time-tagged rotations). If a single Rotation is provided, the frame rotation is static.

  • loc (Location, optional) – A Location object (Point or Path) defining the frame origin in ECEF coordinates. If not provided, the frame origin is assumed to be at the origin of the reference frame. If loc is provided, it must be a singular Point (1x3) for static frames. Use Path objects for time-varying locations.

  • ref_frame (Frame, optional) – A reference Frame object to define the rotation relative to. If not provided, the rotation is assumed to be absolute (e.g., from ECEF frame).

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

  • name (str)

Examples

Static frame with static rotation and location:

>>> from astrix import Frame, Point
>>> from scipy.spatial.transform import Rotation
>>> rot = Rotation.from_euler("xyz", [90, 0, 0], degrees=True)
>>> loc = Point.from_geodet([-27.47, 153.03, 0])
>>> frame_static = Frame(rot, loc)
>>> frame_static.interp_rot().as_euler("xyz", degrees=True)
array([[90.,  0.,  0.]])

Time-varying frame with rotation sequence and static location:

>>> from astrix import Time, RotationSequence
>>> from datetime import datetime, timezone
>>> times = Time.from_datetime([
...     datetime(2021, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
...     datetime(2021, 1, 1, 13, 0, 0, tzinfo=timezone.utc),
... ])
>>> rots = Rotation.from_euler("xyz", [[0, 0, 0], [90, 0, 0]], degrees=True)
>>> rot_seq = RotationSequence(rots, times)
>>> frame_dynamic = Frame(rot_seq, loc)
>>> t_interp = Time.from_datetime(
...     datetime(2021, 1, 1, 12, 30, 0, tzinfo=timezone.utc)
... )
>>> frame_dynamic.interp_rot(t_interp).as_euler("xyz", degrees=True)
array([[45.,  0.,  0.]])

Frame defined relative to another frame:

>>> rot_ref = Rotation.from_euler("xyz", [0, 30, 0], degrees=True)
>>> frame_ref = Frame(rot_ref, loc)
>>> rot_rel = Rotation.from_euler("xyz", [0, 40, 0], degrees=True)
>>> frame = Frame(rot_rel, ref_frame=frame_ref)
>>> frame.interp_rot().as_euler("xyz", degrees=True)
array([[ 0., 70.,  0.]])

Notes

  • If both loc and ref_frame are provided, the new frame location is used and the reference frame location is disregarded.

  • A TimeGroup object is created internally to manage time associations between rotation, location, and reference frame.

  • If the frame is static (single rotation and singular Point), the time properties return TIME_INVARIANT.

  • Use Path objects for time-varying locations.

convert_to(backend)[source]

Convert the Frame object to a different backend.

Return type:

Frame

Parameters:

backend (str | Any | None)

index_loc(index)[source]

Get the location of the frame at the given index.

Warning: This should only be used after downsampling so that rotation indices are time-aligned. Use with caution. Prefer interp_loc for general use.

Return type:

Point

Parameters:

index (int)

index_rot(index)[source]

Get the absolute rotation of the frame at the given index.

Warning: This should only be used after downsampling so that location and rotation indeces align. Prefer interp_rot for general use.

Return type:

Rotation

Parameters:

index (int)

interp_loc(time=Time invariant (n=1), check_bounds=True)[source]

Get the interpolated location of the frame at the given times. If the location is static, time can be None.

Return type:

Point

Parameters:
  • time (Time)

  • check_bounds (bool)

interp_rot(time=Time invariant (n=1), check_bounds=True)[source]

Get the interpolated absolute rotation of the frame at the given times. If all rotations are time invariant, time can be None.

Return type:

Rotation

Parameters:
  • time (Time)

  • check_bounds (bool)

replace_rot(frame_name, new_rot)[source]

Replace a rotation in the rotation chain with a new rotation.

This is an advanced feature and currently only applicable for static rotations. Should primarily be used for optimisation purposes in autograd frameworks, such as correcting misalignment.

Parameters:
  • frame_name (str) – Name of the frame whose rotation is to be replaced.

  • new_rot (Rotation) – New scipy Rotation object to replace the existing rotation.

Return type:

Frame

sample_at_time(time)[source]

Sample the Frame object at specific times, returning a new Frame with time-varying components sampled at those times. The new frame can then be indexed at these times directly to avoid interpolation

Parameters:

time (Time) – Time object specifying the times to sample the Frame at.

Returns:

New Frame object with components sampled at the specified times.

Return type:

Frame

property backend: str

Get the name of the array backend in use (e.g., ‘numpy’, ‘jax’).

property has_ref: bool

Check if the frame has a reference frame.

property is_static: bool

Check if the frame is static (single rotation and singular Point location).

property loc: Location

Get the location of the frame in ECEF coordinates.

property name: str

Get the name of the frame.

property name_chain: list[str]

Get the names of all frames in the rotation chain, from base to current.

property path: Path

Get the time-varying Path of the frame location, if applicable. If the frame location is static, raises AttributeError.

property point: Point

Get the singular Point of the frame location, if applicable. If the frame location is time-varying, raises AttributeError.

property rel_rot: RotationLike

Get the last rotation of the frame relative to the reference frame.

property time_bounds: tuple[Time, Time]

Get the time bounds of the frame as a tuple (start_time, end_time). If the frame is static, returns TIME_INVARIANT.

property time_group: TimeGroup

Get the Time object associated with the frame, if any.

class Path(point, backend=None)[source]

Bases: Location

Path of multiple Point objects with associated Time. Enables interpolation between points over time and calculation of velocity. Must have at least 2 points with associated Time.

Parameters:
  • point (Point | list of Point) – Point object or list of Point objects with associated Time. If a list is provided, all Points must have the same backend and associated Time.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Examples

Instantiating a Path from a list of Points:

>>> from astrix import Point, Time, Path
>>> from datetime import datetime, timezone
>>> times = Time.from_datetime([
...     datetime(2025, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
...     datetime(2025, 1, 1, 12, 0, 1, tzinfo=timezone.utc),
...     datetime(2025, 1, 1, 12, 0, 2, tzinfo=timezone.utc),
... ])
>>> path = Path([
...     Point([1, 2, 0], time=times[0]),
...     Point([2, 3.8, 0.4], time=times[1]),
...     Point([3, 6.0, 1], time=times[2]),
... ])

Interpolate Path and get velocity:

>>> t_interp = Time.from_datetime(
...     datetime(2025, 1, 1, 12, 0, 1, 500000, tzinfo=timezone.utc)
... )
>>> path.interp(t_interp).ecef
array([[2.5, 4.9, 0.7]])
>>> vel = path.vel.interp(t_interp)
>>> vel.magnitude
array([2.48997992])
convert_to(backend)[source]

Convert the Path object to a different backend.

Return type:

Path

Parameters:

backend (str | Any | None)

downsample(dt_max)[source]

Downsample the Path to a maximum time step of dt_max seconds. Note: This function is not JIT-compatible due to data validation checks.

Return type:

Path

Parameters:

dt_max (float)

interp(time, method='linear', check_bounds=True)[source]

Interpolate the Path to the given times using the specified method.

Parameters:
  • time (Time) – Times to interpolate to.

  • method (str, optional) – Interpolation method. Currently only ‘linear’ is supported. Defaults to ‘linear’.

  • check_bounds (bool, optional) – Whether to check if the interpolation times are within the path time bounds. Defaults to True.

Returns:

Interpolated Point object at the given times.

Return type:

Point

time_at_alt(alt)[source]

Find the times when the Path crosses the given altitude (in metres). Uses linear interpolation between points to find the crossing times. Note: This function is not JIT-compatible due to data validation checks.

Return type:

Time

Parameters:

alt (float)

truncate(start_time=None, end_time=None)[source]

Truncate the Path to the given start and end times. If start_time or end_time is None, the Path is truncated to the start or end of the Path respectively.

Note: This function is not JIT-compatible due to data validation checks.

Return type:

Path

Parameters:
  • start_time (Time | None)

  • end_time (Time | None)

property acc: Acceleration

Calculate the acceleration from the Path using central differences.

property end_time: Time

Get the end time of the Path.

property is_singular: bool

Check if the Path object represents a single point. Always False for Path objects.

property points: Point

Get the list of Point objects that make up the Path.

property start_time: Time

Get the start time of the Path.

property vel: Velocity

Calculate the velocity from the Path using central differences.

class Point(ecef, time=Time invariant (n=1), backend=None)[source]

Bases: Location

Point(s) in ECEF coordinates, stored as (x, y, z) in metres. Can represent a single point or multiple points, and can be associated with a Time object for time instances of the points.

Parameters:
  • ecef (Array) – ECEF coordinates as (x, y, z) in metres. Shape (3,) or (1,3) for single points, (n, 3) for multiple points.

  • time (Time, optional) – Time object associated with the points. If provided, the length of time must match the number of points. Defaults to TIME_INVARIANT for static points.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Examples

Single static point:

>>> from astrix import Point
>>> p1 = Point([-5047162.4, 2568329.79, -2924521.17])  # Brisbane ECEF (m)
>>> p1.geodet  # Convert to geodetic (lat, lon, alt)
array([[-27.47, 153.03, 0.0]])
>>> p2 = Point.from_geodet([-27.47, 153.03, 0])
>>> p2.ecef
array([[-5047162.4, 2568329.79, -2924521.17]])

Multiple static points:

>>> pts = Point([
...     [-5047162.4, 2568329.79, -2924521.17],  # Brisbane
...     [-2694045.0, -4293642.0, 3857878.0],  # San Francisco
... ])
>>> pt_bris = pts[0]
>>> len(pts)
2

Dynamic point with time:

>>> from datetime import datetime, timezone
>>> from astrix import Time
>>> times = Time.from_datetime([
...     datetime(2021, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
...     datetime(2021, 1, 1, 13, 0, 0, tzinfo=timezone.utc),
... ])
>>> pts_time = Point([
...     [-5047162.4, 2568329.79, -2924521.17],
...     [-2694045.0, -4293642.0, 3857878.0],
... ], time=times)
>>> pts_time.has_time
True
>>> pts_time.is_singular
False

Notes

  • When associating a Time object, the length of the Time must match the number of points.

  • Use Path objects for interpolating between multiple points over time.

classmethod from_geodet(geodet, time=Time invariant (n=1), backend=None)[source]

Create a Point object from geodetic coordinates (lat, lon, alt). Latitude and longitude are in degrees, altitude is in meters.

Return type:

Point

Parameters:
  • geodet (Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • time (Time)

  • backend (str | Any | None)

classmethod from_list(points)[source]

Create a Point object from a list of Point objects.

Return type:

Point

Parameters:

points (list[Point])

convert_to(backend)[source]

Convert the Point object to a different backend.

Return type:

Point

Parameters:

backend (str | Any | None)

property has_time: bool

Check if the Point has associated Time.

property is_singular: bool

Check if the Point object represents a single point.

class Ray(dir_rel, origin_rel=array([[0., 0., 0.]]), frame=Frame(name=ECEF, static_rot=True, static_loc=True, has_ref=False, time_bounds=(Time invariant (n=1), Time invariant (n=1)), backend=numpy), time=Time invariant (n=1), check=True, backend=None)[source]

Bases: object

A ray defined by an origin point, direction vector, reference frame, and optional time.

Parameters:
  • dir_rel (Array) – Nx3 array of ray direction vectors in local frame. Need not be normalised. E.g., (1, 0, 0) is a ray pointing along axis 1 of reference frame.

  • origin_rel (Array) – 1x3 or Nx3 array defining the ray origin(s) in local frame (meters). Typically (0,0,0) for camera reference frames, or ECEF coordinates for ECEF frame rays.

  • frame (Frame, optional) – Reference frame for the ray origin and direction.

  • time (Time, optional) – Time object associated with the rays. Must be same length as origin if provided. Defaults to TIME_INVARIANT.

  • check (bool, optional) – If True (default), validate non-zero direction vectors; disable for JIT paths.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Notes

  • For calculating metrics (e.g. az/el), the axis are assumed (1) forward, (2) right, (3) down (FRD frame).

  • Although stored in local coordiantes, rays are globally defined by their reference frame.

  • Monotonically increasing time is required for interpolation. But to prevent data-dependent control flow,

    this is not checked on initialization. Use Time.is_increasing to check if needed.

Examples

>>> from astrix import Point, Ray
>>> origin = Point([0.0, 0.0, 0.0])
>>> target = Point([1.0, 0.0, 0.0])
>>> ray = Ray.from_points(target, origin)
>>> ray.unit_rel
array([[1., 0., 0.]])
classmethod from_az_el(az_el, frame=Frame(name=ECEF, static_rot=True, static_loc=True, has_ref=False, time_bounds=(Time invariant (n=1), Time invariant (n=1)), backend=numpy), time=Time invariant (n=1), origin_rel=array([[0., 0., 0.]]), check=True, backend=None)[source]

Create a Ray object from origin points and heading/elevation angles.

Parameters:
  • az_el (Array) – Nx2 array of azimuth and elevation angles in degrees, relative to the reference frame.

  • frame (Frame, optional) – Reference frame for the ray origin and direction. Defaults to ECEF frame.

  • time (Time, optional) – Time object associated with the rays.

  • origin_rel (Array, optional) – Nx3 array of ray origin points in local frame coordinates. Defaults to (0,0,0), which is the reference frame origin.

  • check (bool, optional) – Whether to check input arrays for validity (not JIT compatible). Defaults to True.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Return type:

Ray

classmethod from_camera(pixel, camera, frame, backend=None)[source]

Create a Ray object from pixel coordinates and a camera model.

Parameters:
  • pixel (Pixel) – Pixel object defining the pixel coordinates and optional time.

  • camera (CameraLike) – Camera model defining the camera parameters and orientation.

  • frame (Frame) – Reference frame for the ray origin and direction.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Returns:

Ray object defined by the pixel coordinates and camera model.

Return type:

Ray

classmethod from_points(endpoint, origin, time=Time invariant (n=1), check=True, backend=None)[source]

Create a Ray object from origin and endpoint arrays in ECEF frame.

Parameters:
  • endpoint (Point) – End points (ECEF coordinates). Must be length N.

  • origin (Point) – Origin points (ECEF coordinates). Must be length N or 1.

  • time (Time, optional) – Time object associated with the rays. If TIME_INVARIANT, time is inferred from endpoint or origin when available.

  • check (bool, optional) – If True, validate geometry (non-zero directions). Disable for JIT compatibility. Defaults to True.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Returns:

Ray object defined by the origin and direction from origin to endpoint.

Return type:

Ray

classmethod from_target_frame(target, frame, check_bounds=True, backend=None)[source]

Create a Ray object from a reference frame and target point(s).

Parameters:
  • target (Point) – Target point(s) in ECEF coordinates. Must be length N or 1.

  • frame (Frame) – Reference frame for the ray origin and direction.

  • check_bounds (bool, optional) – If True, ensure target times fall within frame time ranges.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Returns:

Ray object defined by the frame origin and direction to the target point(s).

Return type:

Ray

convert_to(backend)[source]

Convert the Ray object to a different backend.

Return type:

Ray

Parameters:

backend (str | Any | None)

correct_refraction(alt=100000.0)[source]

Apply atmospheric refraction correction to the Ray object using Bennett’s formula. Altitude sets the exponential scale height (metres) for the correction.

Returns:

New Ray object with refraction-corrected direction vectors.

Return type:

Ray

Parameters:

alt (float)

Notes

  • Assumes standard atmospheric conditions.

interp(time, check_bounds=True)[source]

Interpolate the Ray origin and direction to the given times.

Parameters:
  • time (Time) – Times to interpolate to.

  • check_bounds (bool, optional) – Whether to check if the interpolation times are within the ray time bounds. Defaults to True.

Returns:

Interpolated Ray object at the given times.

Return type:

Ray

project_to_cam(camera)[source]

Project the Ray object to pixel coordinates using a camera model.

Parameters:

camera (CameraLike) – Camera model defining the camera parameters and orientation.

Returns:

Pixel object defining the pixel coordinates and associated time.

Return type:

Pixel

Notes

  • The Ray must be defined in the same reference frame as the camera.

  • Rays that do not intersect the image plane will result in NaN pixel coordinates.

replace_frame(frame)[source]

Replace the reference frame of the Ray without changing origin or direction. Not a transformation, but direct replacement. Use with caution.

Parameters:

frame (Frame) – New reference frame for the ray.

Returns:

Ray object with the new reference frame.

Return type:

Ray

to_ecef()[source]

Convert the Ray object to ECEF coordinates.

Return type:

Ray

to_frame(frame)[source]

Convert the Ray object to a different reference frame.

Parameters:

frame (Frame) – Reference frame to convert the ray to.

Returns:

Ray object defined in the new reference frame.

Return type:

Ray

to_ned()[source]

Convert the Ray object to a local NED frame at the ray origin.

Return type:

Ray

property az_el

Return the azimuth and elevation angles from the forward axis in degrees.

property backend: str

Get the name of the array backend in use (e.g., ‘numpy’, ‘jax’).

property frame: Frame

Get the reference Frame of the ray.

property has_time: bool

Check if the Ray has associated Time.

property origin_points: Point

Get the ray origin point(s) as ECEF. Note: this involves a frame transformation. For repeated access, recommend converting the Ray to ECEF frame first using to_ecef().

property origin_rel: Any

Get the ray origin point(s) in the local frame coordinates. Typically zero for camera reference frames, or ECEF coordinates for ECEF frame rays.

property time: Time

Get the associated Time object, if any.

property total_angle: Any

Return the total angle from the forward axis in degrees.

property unit_ecef: Any

Get the unit direction vector(s) of the ray in ECEF frame. Note: this involves a frame transformation. For repeated access, recommend converting the Ray to ECEF frame first using to_ecef().

property unit_rel: Any

Get the unit direction vector(s) of the ray in the local frame coordinates.

class Rotation(quat, normalize=True, copy=True, scalar_first=False)[source]

Bases: object

Rotation in 3 dimensions.

This class provides an interface to initialize from and represent rotations with:

  • Quaternions

  • Rotation Matrices

  • Rotation Vectors

  • Modified Rodrigues Parameters

  • Euler Angles

  • Davenport Angles (Generalized Euler Angles)

The following operations on rotations are supported:

  • Application on vectors

  • Rotation Composition

  • Rotation Inversion

  • Rotation Indexing

A Rotation instance can contain a single rotation transform or rotations of multiple leading dimensions. E.g., it is possible to have an N-dimensional array of (N, M, K) rotations. When applied to other rotations or vectors, standard broadcasting rules apply.

Indexing within a rotation is supported to access a subset of the rotations stored in a Rotation instance.

To create Rotation objects use from_... methods (see examples below). Rotation(...) is not supposed to be instantiated directly.

Parameters:
  • quat (ArrayLike)

  • normalize (bool)

  • copy (bool)

  • scalar_first (bool)

single
__len__()[source]
from_quat()[source]
Parameters:
  • quat (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • scalar_first (bool)

Return type:

Rotation

from_matrix()[source]
Parameters:
  • matrix (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • assume_valid (bool)

Return type:

Rotation

from_rotvec()[source]
Parameters:
  • rotvec (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • degrees (bool)

Return type:

Rotation

from_mrp()[source]
Parameters:

mrp (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

Return type:

Rotation

from_euler()[source]
Parameters:
  • seq (str)

  • angles (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • degrees (bool)

Return type:

Rotation

from_davenport()[source]
Parameters:
  • axes (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • order (str)

  • angles (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] | float)

  • degrees (bool)

Return type:

Rotation

as_quat()[source]
Parameters:
  • canonical (bool)

  • scalar_first (bool)

Return type:

Any

as_matrix()[source]
Return type:

Any

as_rotvec()[source]
Parameters:

degrees (bool)

Return type:

Any

as_mrp()[source]
Return type:

Any

as_euler()[source]
Parameters:
  • seq (str)

  • degrees (bool)

  • suppress_warnings (bool)

Return type:

Any

as_davenport()[source]
Parameters:
  • axes (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • order (str)

  • degrees (bool)

  • suppress_warnings (bool)

Return type:

Any

concatenate()[source]
Parameters:

rotations (Rotation | Iterable[Rotation])

Return type:

Rotation

apply()[source]
Parameters:
  • vectors (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • inverse (bool)

Return type:

Any

__mul__()[source]
__pow__()[source]
inv()[source]
Return type:

Rotation

magnitude()[source]
Return type:

Any

approx_equal()[source]
Parameters:
  • other (Rotation)

  • atol (float | None)

  • degrees (bool)

Return type:

Any

mean()[source]
Parameters:
  • weights (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] | None)

  • axis (None | int | tuple[int, ...])

Return type:

Rotation

reduce()[source]
Parameters:
Return type:

Rotation | tuple[Rotation, Any, Any]

create_group()[source]
Parameters:
  • group (str)

  • axis (str)

Return type:

Rotation

__getitem__()[source]
identity()[source]
Parameters:
  • num (int | None)

  • shape (int | tuple[int, ...] | None)

Return type:

Rotation

random()[source]
Parameters:
  • num (int | None)

  • rng (Generator | None)

  • shape (tuple[int, ...] | None)

Return type:

Rotation

align_vectors()[source]
Parameters:
  • a (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • b (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • weights (Any | Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str] | None)

  • return_sensitivity (bool)

Return type:

tuple[Rotation, float] | tuple[Rotation, float, Any]

See also

Slerp

Notes

Added in version 1.2.0.

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

A Rotation instance can be initialized in any of the above formats and converted to any of the others. The underlying object is independent of the representation used for initialization.

Consider a counter-clockwise rotation of 90 degrees about the z-axis. This corresponds to the following quaternion (in scalar-last format):

>>> r = R.from_quat([0, 0, np.sin(np.pi/4), np.cos(np.pi/4)])

The rotation can be expressed in any of the other formats:

>>> r.as_matrix()
array([[ 2.22044605e-16, -1.00000000e+00,  0.00000000e+00],
[ 1.00000000e+00,  2.22044605e-16,  0.00000000e+00],
[ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]])
>>> r.as_rotvec()
array([0.        , 0.        , 1.57079633])
>>> r.as_euler('zyx', degrees=True)
array([90.,  0.,  0.])

The same rotation can be initialized using a rotation matrix:

>>> r = R.from_matrix([[0, -1, 0],
...                    [1, 0, 0],
...                    [0, 0, 1]])

Representation in other formats:

>>> r.as_quat()
array([0.        , 0.        , 0.70710678, 0.70710678])
>>> r.as_rotvec()
array([0.        , 0.        , 1.57079633])
>>> r.as_euler('zyx', degrees=True)
array([90.,  0.,  0.])

The rotation vector corresponding to this rotation is given by:

>>> r = R.from_rotvec(np.pi/2 * np.array([0, 0, 1]))

Representation in other formats:

>>> r.as_quat()
array([0.        , 0.        , 0.70710678, 0.70710678])
>>> r.as_matrix()
array([[ 2.22044605e-16, -1.00000000e+00,  0.00000000e+00],
       [ 1.00000000e+00,  2.22044605e-16,  0.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]])
>>> r.as_euler('zyx', degrees=True)
array([90.,  0.,  0.])

The from_euler method is quite flexible in the range of input formats it supports. Here we initialize a single rotation about a single axis:

>>> r = R.from_euler('z', 90, degrees=True)

Again, the object is representation independent and can be converted to any other format:

>>> r.as_quat()
array([0.        , 0.        , 0.70710678, 0.70710678])
>>> r.as_matrix()
array([[ 2.22044605e-16, -1.00000000e+00,  0.00000000e+00],
       [ 1.00000000e+00,  2.22044605e-16,  0.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]])
>>> r.as_rotvec()
array([0.        , 0.        , 1.57079633])

It is also possible to initialize multiple rotations in a single instance using any of the from_... functions. Here we initialize a stack of 3 rotations using the from_euler method:

>>> r = R.from_euler('zyx', [
... [90, 0, 0],
... [0, 45, 0],
... [45, 60, 30]], degrees=True)

The other representations also now return a stack of 3 rotations. For example:

>>> r.as_quat()
array([[0.        , 0.        , 0.70710678, 0.70710678],
       [0.        , 0.38268343, 0.        , 0.92387953],
       [0.39190384, 0.36042341, 0.43967974, 0.72331741]])

Applying the above rotations onto a vector:

>>> v = [1, 2, 3]
>>> r.apply(v)
array([[-2.        ,  1.        ,  3.        ],
       [ 2.82842712,  2.        ,  1.41421356],
       [ 2.24452282,  0.78093109,  2.89002836]])

A Rotation instance can be indexed and sliced as if it were an ND array:

>>> r.as_quat()
array([[0.        , 0.        , 0.70710678, 0.70710678],
       [0.        , 0.38268343, 0.        , 0.92387953],
       [0.39190384, 0.36042341, 0.43967974, 0.72331741]])
>>> p = r[0]
>>> p.as_matrix()
array([[ 2.22044605e-16, -1.00000000e+00,  0.00000000e+00],
       [ 1.00000000e+00,  2.22044605e-16,  0.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]])
>>> q = r[1:3]
>>> q.as_quat()
array([[0.        , 0.38268343, 0.        , 0.92387953],
       [0.39190384, 0.36042341, 0.43967974, 0.72331741]])

In fact it can be converted to numpy.array:

>>> r_array = np.asarray(r)
>>> r_array.shape
(3,)
>>> r_array[0].as_matrix()
array([[ 2.22044605e-16, -1.00000000e+00,  0.00000000e+00],
       [ 1.00000000e+00,  2.22044605e-16,  0.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]])

Multiple rotations can be composed using the * operator:

>>> r1 = R.from_euler('z', 90, degrees=True)
>>> r2 = R.from_rotvec([np.pi/4, 0, 0])
>>> v = [1, 2, 3]
>>> r2.apply(r1.apply(v))
array([-2.        , -1.41421356,  2.82842712])
>>> r3 = r2 * r1 # Note the order
>>> r3.apply(v)
array([-2.        , -1.41421356,  2.82842712])

A rotation can be composed with itself using the ** operator:

>>> p = R.from_rotvec([1, 0, 0])
>>> q = p ** 2
>>> q.as_rotvec()
array([2., 0., 0.])

Finally, it is also possible to invert rotations:

>>> r1 = R.from_euler('z', [[90], [45]], degrees=True)
>>> r2 = r1.inv()
>>> r2.as_euler('zyx', degrees=True)
array([[-90.,   0.,   0.],
       [-45.,   0.,   0.]])

The following function can be used to plot rotations with Matplotlib by showing how they transform the standard x, y, z coordinate axes:

>>> import matplotlib.pyplot as plt
>>> def plot_rotated_axes(ax, r, name=None, offset=(0, 0, 0), scale=1):
...     colors = ("#FF6666", "#005533", "#1199EE")  # Colorblind-safe RGB
...     loc = np.array([offset, offset])
...     for i, (axis, c) in enumerate(zip((ax.xaxis, ax.yaxis, ax.zaxis),
...                                       colors)):
...         axlabel = axis.axis_name
...         axis.set_label_text(axlabel)
...         axis.label.set_color(c)
...         axis.line.set_color(c)
...         axis.set_tick_params(colors=c)
...         line = np.zeros((2, 3))
...         line[1, i] = scale
...         line_rot = r.apply(line)
...         line_plot = line_rot + loc
...         ax.plot(line_plot[:, 0], line_plot[:, 1], line_plot[:, 2], c)
...         text_loc = line[1]*1.2
...         text_loc_rot = r.apply(text_loc)
...         text_plot = text_loc_rot + loc[0]
...         ax.text(*text_plot, axlabel.upper(), color=c,
...                 va="center", ha="center")
...     ax.text(*offset, name, color="k", va="center", ha="center",
...             bbox={"fc": "w", "alpha": 0.8, "boxstyle": "circle"})

Create three rotations - the identity and two Euler rotations using intrinsic and extrinsic conventions:

>>> r0 = R.identity()
>>> r1 = R.from_euler("ZYX", [90, -30, 0], degrees=True)  # intrinsic
>>> r2 = R.from_euler("zyx", [90, -30, 0], degrees=True)  # extrinsic

Add all three rotations to a single plot:

>>> ax = plt.figure().add_subplot(projection="3d", proj_type="ortho")
>>> plot_rotated_axes(ax, r0, name="r0", offset=(0, 0, 0))
>>> plot_rotated_axes(ax, r1, name="r1", offset=(3, 0, 0))
>>> plot_rotated_axes(ax, r2, name="r2", offset=(6, 0, 0))
>>> _ = ax.annotate(
...     "r0: Identity Rotation\n"
...     "r1: Intrinsic Euler Rotation (ZYX)\n"
...     "r2: Extrinsic Euler Rotation (zyx)",
...     xy=(0.6, 0.7), xycoords="axes fraction", ha="left"
... )
>>> ax.set(xlim=(-1.25, 7.25), ylim=(-1.25, 1.25), zlim=(-1.25, 1.25))
>>> ax.set(xticks=range(-1, 8), yticks=[-1, 0, 1], zticks=[-1, 0, 1])
>>> ax.set_aspect("equal", adjustable="box")
>>> ax.figure.set_size_inches(6, 5)
>>> plt.tight_layout()

Show the plot:

>>> plt.show()

These examples serve as an overview into the Rotation class and highlight major functionalities. For more thorough examples of the range of input and output formats supported, consult the individual method’s examples.

classmethod create_group(group, axis='Z')[source]

Create a 3D rotation group.

Parameters:
  • group (string) –

    The name of the group. Must be one of ‘I’, ‘O’, ‘T’, ‘Dn’, ‘Cn’, where n is a positive integer. The groups are:

    • I: Icosahedral group

    • O: Octahedral group

    • T: Tetrahedral group

    • D: Dicyclic group

    • C: Cyclic group

  • axis (integer) – The cyclic rotation axis. Must be one of [‘X’, ‘Y’, ‘Z’] (or lowercase). Default is ‘Z’. Ignored for groups ‘I’, ‘O’, and ‘T’.

Returns:

rotation – Object containing the elements of the rotation group.

Return type:

Rotation instance

Notes

This method generates rotation groups only. The full 3-dimensional point groups [PointGroups] also contain reflections.

References

[PointGroups]

Point groups on Wikipedia.

static align_vectors(a, b, weights=None, return_sensitivity=False)[source]

Estimate a rotation to optimally align two sets of vectors.

Find a rotation between frames A and B which best aligns a set of vectors a and b observed in these frames. The following loss function is minimized to solve for the rotation matrix \(C\):

\[L(C) = \frac{1}{2} \sum_{i = 1}^{n} w_i \lVert \mathbf{a}_i - C \mathbf{b}_i \rVert^2 ,\]

where \(w_i\)’s are the weights corresponding to each vector.

The rotation is estimated with Kabsch algorithm [1]_, and solves what is known as the “pointing problem”, or “Wahba’s problem” [2]_.

Note that the length of each vector in this formulation acts as an implicit weight. So for use cases where all vectors need to be weighted equally, you should normalize them to unit length prior to calling this method.

There are two special cases. The first is if a single vector is given for a and b, in which the shortest distance rotation that aligns b to a is returned.

The second is when one of the weights is infinity. In this case, the shortest distance rotation between the primary infinite weight vectors is calculated as above. Then, the rotation about the aligned primary vectors is calculated such that the secondary vectors are optimally aligned per the above loss function. The result is the composition of these two rotations. The result via this process is the same as the Kabsch algorithm as the corresponding weight approaches infinity in the limit. For a single secondary vector this is known as the “align-constrain” algorithm [3]_.

For both special cases (single vectors or an infinite weight), the sensitivity matrix does not have physical meaning and an error will be raised if it is requested. For an infinite weight, the primary vectors act as a constraint with perfect alignment, so their contribution to rssd will be forced to 0 even if they are of different lengths.

Parameters:
  • a (array_like, shape (3,) or (N, 3)) – Vector components observed in initial frame A. Each row of a denotes a vector.

  • b (array_like, shape (3,) or (N, 3)) – Vector components observed in another frame B. Each row of b denotes a vector.

  • weights (array_like shape (N,), optional) – Weights describing the relative importance of the vector observations. If None (default), then all values in weights are assumed to be 1. One and only one weight may be infinity, and weights must be positive.

  • return_sensitivity (bool, optional) – Whether to return the sensitivity matrix. See Notes for details. Default is False.

Return type:

tuple[Rotation, float] | tuple[Rotation, float, Any]

Returns:

  • rotation (Rotation instance) – Best estimate of the rotation that transforms b to a.

  • rssd (float) – Stands for “root sum squared distance”. Square root of the weighted sum of the squared distances between the given sets of vectors after alignment. It is equal to sqrt(2 * minimum_loss), where minimum_loss is the loss function evaluated for the found optimal rotation. Note that the result will also be weighted by the vectors’ magnitudes, so perfectly aligned vector pairs will have nonzero rssd if they are not of the same length. This can be avoided by normalizing them to unit length prior to calling this method, though note that doing this will change the resulting rotation.

  • sensitivity_matrix (ndarray, shape (3, 3)) – Sensitivity matrix of the estimated rotation estimate as explained in Notes. Returned only when return_sensitivity is True. Not valid if aligning a single pair of vectors or if there is an infinite weight, in which cases an error will be raised.

Notes

The sensitivity matrix gives the sensitivity of the estimated rotation to small perturbations of the vector measurements. Specifically we consider the rotation estimate error as a small rotation vector of frame A. The sensitivity matrix is proportional to the covariance of this rotation vector assuming that the vectors in a was measured with errors significantly less than their lengths. To get the true covariance matrix, the returned sensitivity matrix must be multiplied by harmonic mean [4] of variance in each observation. Note that weights are supposed to be inversely proportional to the observation variances to get consistent results. For example, if all vectors are measured with the same accuracy of 0.01 (weights must be all equal), then you should multiple the sensitivity matrix by 0.01**2 to get the covariance.

Refer to [5] for more rigorous discussion of the covariance estimation. See [6] for more discussion of the pointing problem and minimal proper pointing.

This function does not support broadcasting or ND arrays with N > 2.

Array API Standard Support

align_vectors has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> import numpy as np
>>> from scipy.spatial.transform import Rotation as R

Here we run the baseline Kabsch algorithm to best align two sets of vectors, where there is noise on the last two vector measurements of the b set:

>>> a = [[0, 1, 0], [0, 1, 1], [0, 1, 1]]
>>> b = [[1, 0, 0], [1, 1.1, 0], [1, 0.9, 0]]
>>> rot, rssd, sens = R.align_vectors(a, b, return_sensitivity=True)
>>> rot.as_matrix()
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.]])

When we apply the rotation to b, we get vectors close to a:

>>> rot.apply(b)
array([[0. , 1. , 0. ],
       [0. , 1. , 1.1],
       [0. , 1. , 0.9]])

The error for the first vector is 0, and for the last two the error is magnitude 0.1. The rssd is the square root of the sum of the weighted squared errors, and the default weights are all 1, so in this case the rssd is calculated as sqrt(1 * 0**2 + 1 * 0.1**2 + 1 * (-0.1)**2) = 0.141421356237308

>>> a - rot.apply(b)
array([[ 0., 0.,  0. ],
       [ 0., 0., -0.1],
       [ 0., 0.,  0.1]])
>>> np.sqrt(np.sum(np.ones(3) @ (a - rot.apply(b))**2))
0.141421356237308
>>> rssd
0.141421356237308

The sensitivity matrix for this example is as follows:

>>> sens
array([[0.2, 0. , 0.],
       [0. , 1.5, 1.],
       [0. , 1. , 1.]])

Special case 1: Find a minimum rotation between single vectors:

>>> a = [1, 0, 0]
>>> b = [0, 1, 0]
>>> rot, _ = R.align_vectors(a, b)
>>> rot.as_matrix()
array([[0., 1., 0.],
       [-1., 0., 0.],
       [0., 0., 1.]])
>>> rot.apply(b)
array([1., 0., 0.])

Special case 2: One infinite weight. Here we find a rotation between primary and secondary vectors that can align exactly:

>>> a = [[0, 1, 0], [0, 1, 1]]
>>> b = [[1, 0, 0], [1, 1, 0]]
>>> rot, _ = R.align_vectors(a, b, weights=[np.inf, 1])
>>> rot.as_matrix()
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.]])
>>> rot.apply(b)
array([[0., 1., 0.],
       [0., 1., 1.]])

Here the secondary vectors must be best-fit:

>>> a = [[0, 1, 0], [0, 1, 1]]
>>> b = [[1, 0, 0], [1, 2, 0]]
>>> rot, _ = R.align_vectors(a, b, weights=[np.inf, 1])
>>> rot.as_matrix()
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.]])
>>> rot.apply(b)
array([[0., 1., 0.],
       [0., 1., 2.]])
static concatenate(rotations)[source]

Concatenate a sequence of Rotation objects into a single object.

This is useful if you want to, for example, take the mean of a set of rotations and need to pack them into a single object to do so.

Parameters:

rotations (sequence of Rotation objects) – The rotations to concatenate. If a single Rotation object is passed in, a copy is returned.

Returns:

concatenated – The concatenated rotations.

Return type:

Rotation instance

Notes

Added in version 1.8.0.

Array API Standard Support

concatenate has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> r1 = R.from_rotvec([0, 0, 1])
>>> r2 = R.from_rotvec([0, 0, 2])
>>> rc = R.concatenate([r1, r2])
>>> rc.as_rotvec()
array([[0., 0., 1.],
       [0., 0., 2.]])
>>> rc.mean().as_rotvec()
array([0., 0., 1.5])

Concatenation of a split rotation recovers the original object.

>>> rs = [r for r in rc]
>>> R.concatenate(rs).as_rotvec()
array([[0., 0., 1.],
       [0., 0., 2.]])

Note that it may be simpler to create the desired rotations by passing in a single list of the data during initialization, rather then by concatenating:

>>> R.from_rotvec([[0, 0, 1], [0, 0, 2]]).as_rotvec()
array([[0., 0., 1.],
       [0., 0., 2.]])
static from_davenport(axes, order, angles, degrees=False)[source]

Initialize from Davenport angles.

Rotations in 3-D can be represented by a sequence of 3 rotations around a sequence of axes.

The three rotations can either be in a global frame of reference (extrinsic) or in a body centred frame of reference (intrinsic), which is attached to, and moves with, the object under rotation [1]_.

For both Euler angles and Davenport angles, consecutive axes must be are orthogonal (axis2 is orthogonal to both axis1 and axis3). For Euler angles, there is an additional relationship between axis1 or axis3, with two possibilities:

  • axis1 and axis3 are also orthogonal (asymmetric sequence)

  • axis1 == axis3 (symmetric sequence)

For Davenport angles, this last relationship is relaxed [2]_, and only the consecutive orthogonal axes requirement is maintained.

Parameters:
  • axes (array_like, shape (3,) or (..., [1 or 2 or 3], 3)) – Axis of rotation, if one dimensional. If two or more dimensional, describes the sequence of axes for rotations, where each axes[…, i, :] is the ith axis. If more than one axis is given, then the second axis must be orthogonal to both the first and third axes.

  • order (string) – If it is equal to ‘e’ or ‘extrinsic’, the sequence will be extrinsic. If it is equal to ‘i’ or ‘intrinsic’, sequence will be treated as intrinsic.

  • angles (float or array_like, shape (..., [1 or 2 or 3])) – Angles specified in radians (degrees is False) or degrees (degrees is True). Each angle i in the last dimension of angles turns around the corresponding axis axis[…, i, :]. The resulting rotation has the shape np.broadcast_shapes(np.atleast_2d(axes).shape[:-2], np.atleast_1d(angles).shape[:-1]) Dimensionless angles are thus only valid for a single axis.

  • degrees (bool, optional) – If True, then the given angles are assumed to be in degrees. Default is False.

Returns:

rotation – Object containing the rotation represented by the sequence of rotations around given axes with given angles.

Return type:

Rotation instance

Notes

Array API Standard Support

from_davenport has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R

Davenport angles are a generalization of Euler angles, when we use the canonical basis axes:

>>> ex = [1, 0, 0]
>>> ey = [0, 1, 0]
>>> ez = [0, 0, 1]

Initialize a single rotation with a given axis sequence:

>>> axes = [ez, ey, ex]
>>> r = R.from_davenport(axes, 'extrinsic', [90, 0, 0], degrees=True)
>>> r.as_quat().shape
(4,)

It is equivalent to Euler angles in this case:

>>> r.as_euler('zyx', degrees=True)
array([90.,  0., -0.])

Initialize multiple rotations in one object:

>>> r = R.from_davenport(axes, 'extrinsic', [[90, 45, 30], [35, 45, 90]], degrees=True)
>>> r.as_quat().shape
(2, 4)

Using only one or two axes is also possible:

>>> r = R.from_davenport([ez, ex], 'extrinsic', [[90, 45], [35, 45]], degrees=True)
>>> r.as_quat().shape
(2, 4)

Non-canonical axes are possible, and they do not need to be normalized, as long as consecutive axes are orthogonal:

>>> e1 = [2, 0, 0]
>>> e2 = [0, 1, 0]
>>> e3 = [1, 0, 1]
>>> axes = [e1, e2, e3]
>>> r = R.from_davenport(axes, 'extrinsic', [90, 45, 30], degrees=True)
>>> r.as_quat()
[ 0.701057,  0.430459, -0.092296,  0.560986]
static from_euler(seq, angles, degrees=False)[source]

Initialize from Euler angles.

Rotations in 3-D can be represented by a sequence of 3 rotations around a sequence of axes. In theory, any three axes spanning the 3-D Euclidean space are enough. In practice, the axes of rotation are chosen to be the basis vectors.

The three rotations can either be in a global frame of reference (extrinsic) or in a body centred frame of reference (intrinsic), which is attached to, and moves with, the object under rotation [1]_.

Parameters:
  • seq (string) – Specifies sequence of axes for rotations. Up to 3 characters belonging to the set {‘X’, ‘Y’, ‘Z’} for intrinsic rotations, or {‘x’, ‘y’, ‘z’} for extrinsic rotations. Extrinsic and intrinsic rotations cannot be mixed in one function call.

  • angles (float or array_like, shape (..., [1 or 2 or 3])) – Euler angles specified in radians (degrees is False) or degrees (degrees is True). Each character in seq defines one axis around which angles turns. The resulting rotation has the shape np.atleast_1d(angles).shape[:-1]. Dimensionless angles are thus only valid for single character seq.

  • degrees (bool, optional) – If True, then the given angles are assumed to be in degrees. Default is False.

Returns:

rotation – Object containing the rotation represented by the sequence of rotations around given axes with given angles.

Return type:

Rotation instance

Notes

Array API Standard Support

from_euler has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R

Initialize a single rotation along a single axis:

>>> r = R.from_euler('x', 90, degrees=True)
>>> r.as_quat().shape
(4,)

Initialize a single rotation with a given axis sequence:

>>> r = R.from_euler('zyx', [90, 45, 30], degrees=True)
>>> r.as_quat().shape
(4,)

Initialize a stack with a single rotation around a single axis:

>>> r = R.from_euler('x', [[90]], degrees=True)
>>> r.as_quat().shape
(1, 4)

Initialize a stack with a single rotation with an axis sequence:

>>> r = R.from_euler('zyx', [[90, 45, 30]], degrees=True)
>>> r.as_quat().shape
(1, 4)

Initialize multiple elementary rotations in one object:

>>> r = R.from_euler('x', [[90], [45], [30]], degrees=True)
>>> r.as_quat().shape
(3, 4)

Initialize multiple rotations in one object:

>>> r = R.from_euler('zyx', [[90, 45, 30], [35, 45, 90]], degrees=True)
>>> r.as_quat().shape
(2, 4)
static from_matrix(matrix, *, assume_valid=False)[source]

Initialize from rotation matrix.

Rotations in 3 dimensions can be represented with 3 x 3 orthogonal matrices [1]_. If the input is not orthogonal, an approximation is created by orthogonalizing the input matrix using the method described in [2]_, and then converting the orthogonal rotation matrices to quaternions using the algorithm described in [3]_. Matrices must be right-handed.

Parameters:
  • matrix (array_like, shape (..., 3, 3)) – A single matrix or an ND array of matrices, where the last two dimensions contain the rotation matrices.

  • assume_valid (bool, optional) – Must be False unless users can guarantee the input is a valid rotation matrix, i.e. it is orthogonal, rows and columns have unit norm and the determinant is 1. Setting this to True without ensuring these properties is unsafe and will silently lead to incorrect results. If True, normalization steps are skipped, which can improve runtime performance. Default is False.

Returns:

rotation – Object containing the rotations represented by the rotation matrices.

Return type:

Rotation instance

Notes

This function was called from_dcm before.

Added in version 1.4.0.

Array API Standard Support

from_matrix has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Initialize a single rotation:

>>> r = R.from_matrix([
... [0, -1, 0],
... [1, 0, 0],
... [0, 0, 1]])
>>> r.single
True
>>> r.as_matrix().shape
(3, 3)

Initialize multiple rotations in a single object:

>>> r = R.from_matrix([
... [
...     [0, -1, 0],
...     [1, 0, 0],
...     [0, 0, 1],
... ],
... [
...     [1, 0, 0],
...     [0, 0, -1],
...     [0, 1, 0],
... ]])
>>> r.as_matrix().shape
(2, 3, 3)
>>> r.single
False
>>> len(r)
2

If input matrices are not special orthogonal (orthogonal with determinant equal to +1), then a special orthogonal estimate is stored:

>>> a = np.array([
... [0, -0.5, 0],
... [0.5, 0, 0],
... [0, 0, 0.5]])
>>> np.linalg.det(a)
0.125
>>> r = R.from_matrix(a)
>>> matrix = r.as_matrix()
>>> matrix
array([[ 0., -1.,  0.],
       [ 1.,  0.,  0.],
       [ 0.,  0.,  1.]])
>>> np.linalg.det(matrix)
1.0

It is also possible to have a stack containing a single rotation:

>>> r = R.from_matrix([[
... [0, -1, 0],
... [1, 0, 0],
... [0, 0, 1]]])
>>> r.as_matrix()
array([[[ 0., -1.,  0.],
        [ 1.,  0.,  0.],
        [ 0.,  0.,  1.]]])
>>> r.as_matrix().shape
(1, 3, 3)

We can also create an N-dimensional array of rotations:

>>> r = R.from_matrix(np.tile(np.eye(3), (2, 3, 1, 1)))
>>> r.shape
(2, 3)
static from_mrp(mrp)[source]

Initialize from Modified Rodrigues Parameters (MRPs).

MRPs are a 3 dimensional vector co-directional to the axis of rotation and whose magnitude is equal to tan(theta / 4), where theta is the angle of rotation (in radians) [1]_.

MRPs have a singularity at 360 degrees which can be avoided by ensuring the angle of rotation does not exceed 180 degrees, i.e. switching the direction of the rotation when it is past 180 degrees.

Parameters:

mrp (array_like, shape (..., 3)) – A single vector or an ND array of vectors, where the last dimension contains the rotation parameters.

Returns:

rotation – Object containing the rotations represented by input MRPs.

Return type:

Rotation instance

Notes

Added in version 1.6.0.

Array API Standard Support

from_mrp has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Initialize a single rotation:

>>> r = R.from_mrp([0, 0, 1])
>>> r.as_euler('xyz', degrees=True)
array([0.        , 0.        , 180.      ])
>>> r.as_euler('xyz').shape
(3,)

Initialize multiple rotations in one object:

>>> r = R.from_mrp([
... [0, 0, 1],
... [1, 0, 0]])
>>> r.as_euler('xyz', degrees=True)
array([[0.        , 0.        , 180.      ],
       [180.0     , 0.        , 0.        ]])
>>> r.as_euler('xyz').shape
(2, 3)

It is also possible to have a stack of a single rotation:

>>> r = R.from_mrp([[0, 0, np.pi/2]])
>>> r.as_euler('xyz').shape
(1, 3)
static from_quat(quat, *, scalar_first=False)[source]

Initialize from quaternions.

Rotations in 3 dimensions can be represented using unit norm quaternions [1]_.

The 4 components of a quaternion are divided into a scalar part w and a vector part (x, y, z) and can be expressed from the angle theta and the axis n of a rotation as follows:

w = cos(theta / 2)
x = sin(theta / 2) * n_x
y = sin(theta / 2) * n_y
z = sin(theta / 2) * n_z

There are 2 conventions to order the components in a quaternion:

  • scalar-first order – (w, x, y, z)

  • scalar-last order – (x, y, z, w)

The choice is controlled by scalar_first argument. By default, it is False and the scalar-last order is assumed.

Advanced users may be interested in the “double cover” of 3D space by the quaternion representation [2]_. As of version 1.11.0, the following subset (and only this subset) of operations on a Rotation r corresponding to a quaternion q are guaranteed to preserve the double cover property: r = Rotation.from_quat(q), r.as_quat(canonical=False), r.inv(), and composition using the * operator such as r*r.

Parameters:
  • quat (array_like, shape (..., 4)) – Each row is a (possibly non-unit norm) quaternion representing an active rotation. Each quaternion will be normalized to unit norm.

  • scalar_first (bool, optional) – Whether the scalar component goes first or last. Default is False, i.e. the scalar-last order is assumed.

Returns:

rotation – Object containing the rotations represented by input quaternions.

Return type:

Rotation instance

Notes

Array API Standard Support

from_quat has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R

A rotation can be initialized from a quaternion with the scalar-last (default) or scalar-first component order as shown below:

>>> r = R.from_quat([0, 0, 0, 1])
>>> r.as_matrix()
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
>>> r = R.from_quat([1, 0, 0, 0], scalar_first=True)
>>> r.as_matrix()
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])

It is possible to initialize multiple rotations in a single object by passing an N-dimensional array:

>>> r = R.from_quat([[
... [1, 0, 0, 0],
... [0, 0, 0, 1]
... ]])
>>> r.as_quat()
array([[[1., 0., 0., 0.],
        [0., 0., 0., 1.]]])
>>> r.as_quat().shape
(1, 2, 4)

It is also possible to have a stack of a single rotation:

>>> r = R.from_quat([[0, 0, 0, 1]])
>>> r.as_quat()
array([[0., 0., 0., 1.]])
>>> r.as_quat().shape
(1, 4)

Quaternions are normalized before initialization.

>>> r = R.from_quat([0, 0, 1, 1])
>>> r.as_quat()
array([0.        , 0.        , 0.70710678, 0.70710678])
static from_rotvec(rotvec, degrees=False)[source]

Initialize from rotation vectors.

A rotation vector is a 3 dimensional vector which is co-directional to the axis of rotation and whose norm gives the angle of rotation [1]_.

Parameters:
  • rotvec (array_like, shape (..., 3)) – A single vector or an ND array of vectors, where the last dimension contains the rotation vectors.

  • degrees (bool, optional) –

    If True, then the given magnitudes are assumed to be in degrees. Default is False.

    Added in version 1.7.0.

Returns:

rotation – Object containing the rotations represented by input rotation vectors.

Return type:

Rotation instance

Notes

Array API Standard Support

from_rotvec has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Initialize a single rotation:

>>> r = R.from_rotvec(np.pi/2 * np.array([0, 0, 1]))
>>> r.as_rotvec()
array([0.        , 0.        , 1.57079633])
>>> r.as_rotvec().shape
(3,)

Initialize a rotation in degrees, and view it in degrees:

>>> r = R.from_rotvec(45 * np.array([0, 1, 0]), degrees=True)
>>> r.as_rotvec(degrees=True)
array([ 0., 45.,  0.])

Initialize multiple rotations in one object:

>>> r = R.from_rotvec([
... [0, 0, np.pi/2],
... [np.pi/2, 0, 0]])
>>> r.as_rotvec()
array([[0.        , 0.        , 1.57079633],
       [1.57079633, 0.        , 0.        ]])
>>> r.as_rotvec().shape
(2, 3)

It is also possible to have a stack of a single rotation:

>>> r = R.from_rotvec([[0, 0, np.pi/2]])
>>> r.as_rotvec().shape
(1, 3)
static identity(num=None, *, shape=None)[source]

Get identity rotation(s).

Composition with the identity rotation has no effect.

Parameters:
  • num (int or None, optional) – Number of identity rotations to generate. If None (default), then a single rotation is generated.

  • shape (int or tuple of ints, optional) – Shape of identity rotations to generate. If specified, num must be None.

Returns:

identity – The identity rotation.

Return type:

Rotation object

static random(num=None, rng=None, *, shape=None)[source]

Generate rotations that are uniformly distributed on a sphere.

Formally, the rotations follow the Haar-uniform distribution over the SO(3) group.

Parameters:
  • num (int or None, optional) – Number of random rotations to generate. If None (default), then a single rotation is generated.

  • rng ({None, int, numpy.random.Generator}, optional) –

    If rng is passed by keyword, types other than numpy.random.Generator are passed to numpy.random.default_rng to instantiate a Generator. If rng is already a Generator instance, then the provided instance is used. Specify rng for repeatable function behavior.

    If this argument is passed by position or random_state is passed by keyword, legacy behavior for the argument random_state applies:

    • If random_state is None (or numpy.random), the numpy.random.RandomState singleton is used.

    • If random_state is an int, a new RandomState instance is used, seeded with random_state.

    • If random_state is already a Generator or RandomState instance then that instance is used.

    Changed in version 1.15.0: As part of the SPEC-007 transition from use of numpy.random.RandomState to numpy.random.Generator, this keyword was changed from random_state to rng. For an interim period, both keywords will continue to work, although only one may be specified at a time. After the interim period, function calls using the random_state keyword will emit warnings. The behavior of both random_state and rng are outlined above, but only the rng keyword should be used in new code.

  • shape (tuple of ints, optional) – Shape of random rotations to generate. If specified, num must be None.

Returns:

random_rotation – Contains a single rotation if num is None. Otherwise contains a stack of num rotations.

Return type:

Rotation instance

See also

scipy.stats.special_ortho_group()

Notes

This function is optimized for efficiently sampling random rotation matrices in three dimensions. For generating random rotation matrices in higher dimensions, see scipy.stats.special_ortho_group.

Examples

>>> from scipy.spatial.transform import Rotation as R

Sample a single rotation:

>>> R.random().as_euler('zxy', degrees=True)
array([-110.5976185 ,   55.32758512,   76.3289269 ])  # random

Sample a stack of rotations:

>>> R.random(5).as_euler('zxy', degrees=True)
array([[-110.5976185 ,   55.32758512,   76.3289269 ],  # random
       [ -91.59132005,  -14.3629884 ,  -93.91933182],
       [  25.23835501,   45.02035145, -121.67867086],
       [ -51.51414184,  -15.29022692, -172.46870023],
       [ -81.63376847,  -27.39521579,    2.60408416]])
apply(vectors, inverse=False)[source]

Apply this rotation to a set of vectors.

If the original frame rotates to the final frame by this rotation, then its application to a vector can be seen in two ways:

  • As a projection of vector components expressed in the final frame to the original frame.

  • As the physical rotation of a vector being glued to the original frame as it rotates. In this case the vector components are expressed in the original frame before and after the rotation.

In terms of rotation matrices, this application is the same as self.as_matrix() @ vectors.

Parameters:
  • vectors (array_like, shape (..., 3)) – Each vectors[…, :] represents a vector in 3D space. The shape of rotations and shape of vectors given must follow standard numpy broadcasting rules: either one of them equals unity or they both equal each other.

  • inverse (boolean, optional) – If True then the inverse of the rotation(s) is applied to the input vectors. Default is False.

Returns:

rotated_vectors – Result of applying rotation on input vectors. Shape is determined according to numpy broadcasting rules. I.e., the result will have the shape np.broadcast_shapes(r.shape, v.shape[:-1]) + (3,)

Return type:

ndarray, shape (…, 3)

Notes

Array API Standard Support

apply has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Single rotation applied on a single vector:

>>> vector = np.array([1, 0, 0])
>>> r = R.from_rotvec([0, 0, np.pi/2])
>>> r.as_matrix()
array([[ 2.22044605e-16, -1.00000000e+00,  0.00000000e+00],
       [ 1.00000000e+00,  2.22044605e-16,  0.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]])
>>> r.apply(vector)
array([2.22044605e-16, 1.00000000e+00, 0.00000000e+00])
>>> r.apply(vector).shape
(3,)

Single rotation applied on multiple vectors:

>>> vectors = np.array([
... [1, 0, 0],
... [1, 2, 3]])
>>> r = R.from_rotvec([0, 0, np.pi/4])
>>> r.as_matrix()
array([[ 0.70710678, -0.70710678,  0.        ],
       [ 0.70710678,  0.70710678,  0.        ],
       [ 0.        ,  0.        ,  1.        ]])
>>> r.apply(vectors)
array([[ 0.70710678,  0.70710678,  0.        ],
       [-0.70710678,  2.12132034,  3.        ]])
>>> r.apply(vectors).shape
(2, 3)

Multiple rotations on a single vector:

>>> r = R.from_rotvec([[0, 0, np.pi/4], [np.pi/2, 0, 0]])
>>> vector = np.array([1,2,3])
>>> r.as_matrix()
array([[[ 7.07106781e-01, -7.07106781e-01,  0.00000000e+00],
        [ 7.07106781e-01,  7.07106781e-01,  0.00000000e+00],
        [ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]],
       [[ 1.00000000e+00,  0.00000000e+00,  0.00000000e+00],
        [ 0.00000000e+00,  2.22044605e-16, -1.00000000e+00],
        [ 0.00000000e+00,  1.00000000e+00,  2.22044605e-16]]])
>>> r.apply(vector)
array([[-0.70710678,  2.12132034,  3.        ],
       [ 1.        , -3.        ,  2.        ]])
>>> r.apply(vector).shape
(2, 3)

Multiple rotations on multiple vectors. Each rotation is applied on the corresponding vector:

>>> r = R.from_euler('zxy', [
... [0, 0, 90],
... [45, 30, 60]], degrees=True)
>>> vectors = [
... [1, 2, 3],
... [1, 0, -1]]
>>> r.apply(vectors)
array([[ 3.        ,  2.        , -1.        ],
       [-0.09026039,  1.11237244, -0.86860844]])
>>> r.apply(vectors).shape
(2, 3)

Broadcasting rules apply:

>>> r = R.from_rotvec(np.tile([0, 0, np.pi/4], (5, 1, 4, 1)))
>>> vectors = np.ones((3, 4, 3))
>>> r.shape, vectors.shape
((5, 1, 4), (3, 4, 3))
>>> r.apply(vectors).shape
(5, 3, 4, 3)

It is also possible to apply the inverse rotation:

>>> r = R.from_euler('zxy', [
... [0, 0, 90],
... [45, 30, 60]], degrees=True)
>>> vectors = [
... [1, 2, 3],
... [1, 0, -1]]
>>> r.apply(vectors, inverse=True)
array([[-3.        ,  2.        ,  1.        ],
       [ 1.09533535, -0.8365163 ,  0.3169873 ]])
approx_equal(other, atol=None, degrees=False)[source]

Determine if another rotation is approximately equal to this one.

Equality is measured by calculating the smallest angle between the rotations, and checking to see if it is smaller than atol.

Parameters:
  • other (Rotation instance) – Object containing the rotations to measure against this one.

  • atol (float, optional) – The absolute angular tolerance, below which the rotations are considered equal. If not given, then set to 1e-8 radians by default.

  • degrees (bool, optional) – If True and atol is given, then atol is measured in degrees. If False (default), then atol is measured in radians.

Returns:

approx_equal – Whether the rotations are approximately equal, bool if object contains a single rotation and ndarray if object contains multiple rotations.

Return type:

ndarray or bool

Notes

Array API Standard Support

approx_equal has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np
>>> p = R.from_quat([0, 0, 0, 1])
>>> q = R.from_quat(np.eye(4))
>>> p.approx_equal(q)
array([False, False, False, True])

Approximate equality for a single rotation:

>>> p.approx_equal(q[0])
False
as_davenport(axes, order, degrees=False, *, suppress_warnings=False)[source]

Represent as Davenport angles.

Any orientation can be expressed as a composition of 3 elementary rotations.

For both Euler angles and Davenport angles, consecutive axes must be are orthogonal (axis2 is orthogonal to both axis1 and axis3). For Euler angles, there is an additional relationship between axis1 or axis3, with two possibilities:

  • axis1 and axis3 are also orthogonal (asymmetric sequence)

  • axis1 == axis3 (symmetric sequence)

For Davenport angles, this last relationship is relaxed [1]_, and only the consecutive orthogonal axes requirement is maintained.

A slightly modified version of the algorithm from [2]_ has been used to calculate Davenport angles for the rotation about a given sequence of axes.

Davenport angles, just like Euler angles, suffer from the problem of gimbal lock [3]_, where the representation loses a degree of freedom and it is not possible to determine the first and third angles uniquely. In this case, a warning is raised (unless the suppress_warnings option is used), and the third angle is set to zero. Note however that the returned angles still represent the correct rotation.

Parameters:
  • axes (array_like, shape (..., [1 or 2 or 3], 3) or (..., 3)) – Axis of rotation, if one dimensional. If N dimensional, describes the sequence of axes for rotations, where each axes[…, i, :] is the ith axis. If more than one axis is given, then the second axis must be orthogonal to both the first and third axes.

  • order (string) – If it belongs to the set {‘e’, ‘extrinsic’}, the sequence will be extrinsic. If it belongs to the set {‘i’, ‘intrinsic’}, sequence will be treated as intrinsic.

  • degrees (boolean, optional) – Returned angles are in degrees if this flag is True, else they are in radians. Default is False.

  • suppress_warnings (boolean, optional) – Disable warnings about gimbal lock. Default is False.

Returns:

angles – Shape depends on shape of inputs used to initialize object. The returned angles are in the range:

  • First angle belongs to [-180, 180] degrees (both inclusive)

  • Third angle belongs to [-180, 180] degrees (both inclusive)

  • Second angle belongs to a set of size 180 degrees, given by: [-abs(lambda), 180 - abs(lambda)], where lambda is the angle between the first and third axes.

Return type:

ndarray, shape (…, 3)

Notes

Array API Standard Support

as_davenport has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Davenport angles are a generalization of Euler angles, when we use the canonical basis axes:

>>> ex = [1, 0, 0]
>>> ey = [0, 1, 0]
>>> ez = [0, 0, 1]

Represent a single rotation:

>>> r = R.from_rotvec([0, 0, np.pi/2])
>>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True)
array([90.,  0.,  0.])
>>> r.as_euler('zxy', degrees=True)
array([90.,  0.,  0.])
>>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True).shape
(3,)

Represent a stack of single rotation:

>>> r = R.from_rotvec([[0, 0, np.pi/2]])
>>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True)
array([[90.,  0.,  0.]])
>>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True).shape
(1, 3)

Represent multiple rotations in a single object:

>>> r = R.from_rotvec([
... [0, 0, 90],
... [45, 0, 0]], degrees=True)
>>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True)
array([[90.,  0.,  0.],
       [ 0., 45.,  0.]])
>>> r.as_davenport([ez, ex, ey], 'extrinsic', degrees=True).shape
(2, 3)
as_euler(seq, degrees=False, *, suppress_warnings=False)[source]

Represent as Euler angles.

Any orientation can be expressed as a composition of 3 elementary rotations. Once the axis sequence has been chosen, Euler angles define the angle of rotation around each respective axis [1]_.

The algorithm from [2]_ has been used to calculate Euler angles for the rotation about a given sequence of axes.

Euler angles suffer from the problem of gimbal lock [3]_, where the representation loses a degree of freedom and it is not possible to determine the first and third angles uniquely. In this case, a warning is raised (unless the suppress_warnings option is used), and the third angle is set to zero. Note however that the returned angles still represent the correct rotation.

Parameters:
  • seq (string, length 3) – 3 characters belonging to the set {‘X’, ‘Y’, ‘Z’} for intrinsic rotations, or {‘x’, ‘y’, ‘z’} for extrinsic rotations [1]_. Adjacent axes cannot be the same. Extrinsic and intrinsic rotations cannot be mixed in one function call.

  • degrees (boolean, optional) – Returned angles are in degrees if this flag is True, else they are in radians. Default is False.

  • suppress_warnings (boolean, optional) – Disable warnings about gimbal lock. Default is False.

Returns:

angles – Shape depends on shape of inputs used to initialize object. The returned angles are in the range:

  • First angle belongs to [-180, 180] degrees (both inclusive)

  • Third angle belongs to [-180, 180] degrees (both inclusive)

  • Second angle belongs to:

    • [-90, 90] degrees if all axes are different (like xyz)

    • [0, 180] degrees if first and third axes are the same (like zxz)

Return type:

ndarray, shape (…, 3)

Notes

Array API Standard Support

as_euler has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Represent a single rotation:

>>> r = R.from_rotvec([0, 0, np.pi/2])
>>> r.as_euler('zxy', degrees=True)
array([90.,  0.,  0.])
>>> r.as_euler('zxy', degrees=True).shape
(3,)

Represent a stack of single rotation:

>>> r = R.from_rotvec([[0, 0, np.pi/2]])
>>> r.as_euler('zxy', degrees=True)
array([[90.,  0.,  0.]])
>>> r.as_euler('zxy', degrees=True).shape
(1, 3)

Represent multiple rotations in a single object:

>>> r = R.from_rotvec([
... [0, 0, np.pi/2],
... [0, -np.pi/3, 0],
... [np.pi/4, 0, 0]])
>>> r.as_euler('zxy', degrees=True)
array([[ 90.,   0.,   0.],
       [  0.,   0., -60.],
       [  0.,  45.,   0.]])
>>> r.as_euler('zxy', degrees=True).shape
(3, 3)
as_matrix()[source]

Represent as rotation matrix.

3D rotations can be represented using rotation matrices, which are 3 x 3 real orthogonal matrices with determinant equal to +1 [1]_.

Returns:

matrix – Shape depends on shape of inputs used for initialization.

Return type:

ndarray, shape (…, 3)

Notes

This function was called as_dcm before.

Added in version 1.4.0.

Array API Standard Support

as_matrix has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Represent a single rotation:

>>> r = R.from_rotvec([0, 0, np.pi/2])
>>> r.as_matrix()
array([[ 2.22044605e-16, -1.00000000e+00,  0.00000000e+00],
       [ 1.00000000e+00,  2.22044605e-16,  0.00000000e+00],
       [ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]])
>>> r.as_matrix().shape
(3, 3)

Represent a stack with a single rotation:

>>> r = R.from_quat([[1, 1, 0, 0]])
>>> r.as_matrix()
array([[[ 0.,  1.,  0.],
        [ 1.,  0.,  0.],
        [ 0.,  0., -1.]]])
>>> r.as_matrix().shape
(1, 3, 3)

Represent multiple rotations:

>>> r = R.from_rotvec([[np.pi/2, 0, 0], [0, 0, np.pi/2]])
>>> r.as_matrix()
array([[[ 1.00000000e+00,  0.00000000e+00,  0.00000000e+00],
        [ 0.00000000e+00,  2.22044605e-16, -1.00000000e+00],
        [ 0.00000000e+00,  1.00000000e+00,  2.22044605e-16]],
       [[ 2.22044605e-16, -1.00000000e+00,  0.00000000e+00],
        [ 1.00000000e+00,  2.22044605e-16,  0.00000000e+00],
        [ 0.00000000e+00,  0.00000000e+00,  1.00000000e+00]]])
>>> r.as_matrix().shape
(2, 3, 3)
as_mrp()[source]

Represent as Modified Rodrigues Parameters (MRPs).

MRPs are a 3 dimensional vector co-directional to the axis of rotation and whose magnitude is equal to tan(theta / 4), where theta is the angle of rotation (in radians) [1]_.

MRPs have a singularity at 360 degrees which can be avoided by ensuring the angle of rotation does not exceed 180 degrees, i.e. switching the direction of the rotation when it is past 180 degrees. This function will always return MRPs corresponding to a rotation of less than or equal to 180 degrees.

Returns:

mrps – Shape depends on shape of inputs used for initialization.

Return type:

ndarray, shape (…, 3)

Notes

Added in version 1.6.0.

Array API Standard Support

as_mrp has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Represent a single rotation:

>>> r = R.from_rotvec([0, 0, np.pi])
>>> r.as_mrp()
array([0.        , 0.        , 1.         ])
>>> r.as_mrp().shape
(3,)

Represent a stack with a single rotation:

>>> r = R.from_euler('xyz', [[180, 0, 0]], degrees=True)
>>> r.as_mrp()
array([[1.       , 0.        , 0.         ]])
>>> r.as_mrp().shape
(1, 3)

Represent multiple rotations:

>>> r = R.from_rotvec([[np.pi/2, 0, 0], [0, 0, np.pi/2]])
>>> r.as_mrp()
array([[0.41421356, 0.        , 0.        ],
       [0.        , 0.        , 0.41421356]])
>>> r.as_mrp().shape
(2, 3)
as_quat(canonical=False, *, scalar_first=False)[source]

Represent as quaternions.

Rotations in 3 dimensions can be represented using unit norm quaternions [1]_.

The 4 components of a quaternion are divided into a scalar part w and a vector part (x, y, z) and can be expressed from the angle theta and the axis n of a rotation as follows:

w = cos(theta / 2)
x = sin(theta / 2) * n_x
y = sin(theta / 2) * n_y
z = sin(theta / 2) * n_z

There are 2 conventions to order the components in a quaternion:

  • scalar-first order – (w, x, y, z)

  • scalar-last order – (x, y, z, w)

The choice is controlled by scalar_first argument. By default, it is False and the scalar-last order is used.

The mapping from quaternions to rotations is two-to-one, i.e. quaternions q and -q, where -q simply reverses the sign of each component, represent the same spatial rotation.

Parameters:
  • canonical (bool, default False) – Whether to map the redundant double cover of rotation space to a unique “canonical” single cover. If True, then the quaternion is chosen from {q, -q} such that the w term is positive. If the w term is 0, then the quaternion is chosen such that the first nonzero term of the x, y, and z terms is positive.

  • scalar_first (bool, optional) – Whether the scalar component goes first or last. Default is False, i.e. the scalar-last order is used.

Returns:

quat – Shape depends on shape of inputs used for initialization.

Return type:

numpy.ndarray, shape (…, 4)

Notes

Array API Standard Support

as_quat has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

A rotation can be represented as a quaternion with either scalar-last (default) or scalar-first component order. This is shown for a single rotation:

>>> r = R.from_matrix(np.eye(3))
>>> r.as_quat()
array([0., 0., 0., 1.])
>>> r.as_quat(scalar_first=True)
array([1., 0., 0., 0.])

The resulting shape of the quaternion is always the shape of the Rotation object with an added last dimension of size 4. E.g. when the Rotation object contains an N-dimensional array (N, M, K) of rotations, the result will be a 4-dimensional array:

>>> r = R.from_rotvec(np.ones((2, 3, 4, 3)))
>>> r.as_quat().shape
(2, 3, 4, 4)

Quaternions can be mapped from a redundant double cover of the rotation space to a canonical representation with a positive w term.

>>> r = R.from_quat([0, 0, 0, -1])
>>> r.as_quat()
array([0. , 0. , 0. , -1.])
>>> r.as_quat(canonical=True)
array([0. , 0. , 0. , 1.])
as_rotvec(degrees=False)[source]

Represent as rotation vectors.

A rotation vector is a 3 dimensional vector which is co-directional to the axis of rotation and whose norm gives the angle of rotation [1]_.

Parameters:

degrees (boolean, optional) –

Returned magnitudes are in degrees if this flag is True, else they are in radians. Default is False.

Added in version 1.7.0.

Returns:

rotvec – Shape depends on shape of inputs used for initialization.

Return type:

ndarray, shape (…, 3)

Notes

Array API Standard Support

as_rotvec has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Represent a single rotation:

>>> r = R.from_euler('z', 90, degrees=True)
>>> r.as_rotvec()
array([0.        , 0.        , 1.57079633])
>>> r.as_rotvec().shape
(3,)

Represent a rotation in degrees:

>>> r = R.from_euler('YX', (-90, -90), degrees=True)
>>> s = r.as_rotvec(degrees=True)
>>> s
array([-69.2820323, -69.2820323, -69.2820323])
>>> np.linalg.norm(s)
120.00000000000001

Represent a stack with a single rotation:

>>> r = R.from_quat([[0, 0, 1, 1]])
>>> r.as_rotvec()
array([[0.        , 0.        , 1.57079633]])
>>> r.as_rotvec().shape
(1, 3)

Represent multiple rotations in a single object:

>>> r = R.from_quat([[0, 0, 1, 1], [1, 1, 0, 1]])
>>> r.as_rotvec()
array([[0.        , 0.        , 1.57079633],
       [1.35102172, 1.35102172, 0.        ]])
>>> r.as_rotvec().shape
(2, 3)
inv()[source]

Invert this rotation.

Composition of a rotation with its inverse results in an identity transformation.

Returns:

inverse – Object containing inverse of the rotations in the current instance.

Return type:

Rotation instance

Notes

Array API Standard Support

inv has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np

Inverting a single rotation:

>>> p = R.from_euler('z', 45, degrees=True)
>>> q = p.inv()
>>> q.as_euler('zyx', degrees=True)
array([-45.,   0.,   0.])

Inverting multiple rotations:

>>> p = R.from_rotvec([[0, 0, np.pi/3], [-np.pi/4, 0, 0]])
>>> q = p.inv()
>>> q.as_rotvec()
array([[-0.        , -0.        , -1.04719755],
       [ 0.78539816, -0.        , -0.        ]])
magnitude()[source]

Get the magnitude(s) of the rotation(s).

Returns:

magnitude – Angle(s) in radians, float if object contains a single rotation and ndarray if object contains ND rotations. The magnitude will always be in the range [0, pi].

Return type:

ndarray or float

Notes

Array API Standard Support

magnitude has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> import numpy as np
>>> r = R.from_quat(np.eye(4))
>>> r.as_quat()
array([[ 1., 0., 0., 0.],
       [ 0., 1., 0., 0.],
       [ 0., 0., 1., 0.],
       [ 0., 0., 0., 1.]])
>>> r.magnitude()
array([3.14159265, 3.14159265, 3.14159265, 0.        ])

Magnitude of a single rotation:

>>> r[0].magnitude()
3.141592653589793
mean(weights=None, axis=None)[source]

Get the mean of the rotations.

The mean used is the chordal L2 mean (also called the projected or induced arithmetic mean) [1]_. If A is a set of rotation matrices, then the mean M is the rotation matrix that minimizes the following loss function:

\[L(M) = \sum_{i = 1}^{n} w_i \lVert \mathbf{A}_i - \mathbf{M} \rVert^2 ,\]

where \(w_i\)’s are the weights corresponding to each matrix.

Parameters:
  • weights (array_like shape (..., N), optional) – Weights describing the relative importance of the rotations. If None (default), then all values in weights are assumed to be equal. If given, the shape of weights must be broadcastable to the rotation shape. Weights must be non-negative.

  • axis (None, int, or tuple of ints, optional) – Axis or axes along which the means are computed. The default is to compute the mean of all rotations.

Returns:

mean – Single rotation containing the mean of the rotations in the current instance.

Return type:

Rotation instance

Notes

Array API Standard Support

mean has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

References

Examples

>>> from scipy.spatial.transform import Rotation as R
>>> r = R.from_euler('zyx', [[0, 0, 0],
...                          [1, 0, 0],
...                          [0, 1, 0],
...                          [0, 0, 1]], degrees=True)
>>> r.mean().as_euler('zyx', degrees=True)
array([0.24945696, 0.25054542, 0.24945696])
reduce(left=None, right=None, return_indices=False)[source]

Reduce this rotation with the provided rotation groups.

Reduction of a rotation p is a transformation of the form q = l * p * r, where l and r are chosen from left and right respectively, such that rotation q has the smallest magnitude.

If left and right are rotation groups representing symmetries of two objects rotated by p, then q is the rotation of the smallest magnitude to align these objects considering their symmetries.

Parameters:
  • left (Rotation instance, optional) – Object containing the left rotation(s). Default value (None) corresponds to the identity rotation.

  • right (Rotation instance, optional) – Object containing the right rotation(s). Default value (None) corresponds to the identity rotation.

  • return_indices (bool, optional) – Whether to return the indices of the rotations from left and right used for reduction.

Return type:

Rotation | tuple[Rotation, Any, Any]

Returns:

  • reduced (Rotation instance) – Object containing reduced rotations.

  • left_best, right_best (integer ndarray) – Indices of elements from left and right used for reduction.

Notes

Array API Standard Support

reduce has experimental support for Python Array API Standard compatible backends in addition to NumPy. Please consider testing these features by setting an environment variable SCIPY_ARRAY_API=1 and providing CuPy, PyTorch, JAX, or Dask arrays as array arguments. The following combinations of backend and device (or other capability) are supported.

Library

CPU

GPU

NumPy

n/a

CuPy

n/a

PyTorch

JAX

Dask

n/a

See dev-arrayapi for more information.

property shape: tuple[int, ...]

The shape of the rotation’s leading dimensions.

property single: bool

Whether this instance represents a single rotation.

class RotationSequence(rot, time, backend=None)[source]

Bases: RotationLike

A sequence of time-tagged rotations, enabling interpolation between them. Uses scipy.spatial.transform.Slerp for interpolation.

Parameters:
  • rot (Rotation | list of Rotation) – A scipy Rotation object containing multiple rotations, or a list of such objects. If a list is provided, all elements must be scipy Rotation objects.

  • time (Time) – A Time object with time instances corresponding to each rotation. Must be the same length as the number of rotations and strictly increasing.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Examples

>>> from astrix import Time, RotationSequence
>>> from scipy.spatial.transform import Rotation
>>> from datetime import datetime, timezone
>>> times = Time.from_datetime(
...     [
...         datetime(2021, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
...         datetime(2021, 1, 1, 13, 0, 0, tzinfo=timezone.utc),
...         datetime(2021, 1, 1, 14, 0, 0, tzinfo=timezone.utc),
...     ]
... )
>>> rots = Rotation.from_euler(
...     "xyz",
...     [
...         [0, 0, 0],
...         [90, 0, 0],
...         [180, 0, 0],
...     ],
...     degrees=True,
... )
>>> rot_seq = RotationSequence(rots, times)
>>> interp_rot = rot_seq.interp(
...     Time.from_datetime(datetime(2021, 1, 1, 12, 30, 0, tzinfo=timezone.utc))
... )  # Interpolate to halfway between first and second rotation
>>> interp_rot.as_euler(
...     "xyz", degrees=True
... )  # Get interpolated rotation as Euler angles
array([[45.,  0.,  0.]])
convert_to(backend)[source]

Convert the RotationSequence object to a different backend.

Return type:

RotationSequence

Parameters:

backend (str | Any | None)

downsample(dt_max)[source]

Downsample the rotation sequence to a coarser time resolution.

Parameters:

dt_max (float) – Desired maximum time step in seconds for downsampling.

Returns:

A new RotationSequence object with downsampled rotations.

Return type:

RotationSequence

interp(time, check_bounds=True)[source]

Interpolate the rotation sequence at the given times to return Rotation(s).

Return type:

Rotation

Parameters:
  • time (Time)

  • check_bounds (bool)

property rots: Rotation

Get the underlying scipy Rotation object containing all rotations.

property time: Time

Get the Time object associated with the rotation sequence.

class Velocity(vec, time, _xp)[source]

Bases: object

Velocity vector(s) in ECEF coordinates (vx, vy, vz) in m/s. Associated with a Time object for the time instances of the velocities. Internal use only, typically created from Path objects. No data validation is performed.

Parameters:
  • vec (Array) – Velocity vectors in ECEF coordinates (vx, vy, vz) in m/s. Shape (n, 3).

  • time (Time) – Time object associated with the velocities. Length must match number of velocity vectors.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

  • _xp (Any)

Examples

Velocity objects are typically created from Path objects.

>>> from astrix import Point, Time, Path
>>> from datetime import datetime, timezone
>>> times = Time.from_datetime([
...     datetime(2025, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
...     datetime(2025, 1, 1, 12, 0, 1, tzinfo=timezone.utc),
...     datetime(2025, 1, 1, 12, 0, 2, tzinfo=timezone.utc),
... ])
>>> path = Path([
...     Point([1, 2, 0], time=times[0]),
...     Point([2, 3.8, 0.4], time=times[1]),
...     Point([3, 6.0, 1], time=times[2]),
... ])
>>> vel = path.vel
>>> vel.magnitude
array([1.91049732, 2.29128785, 2.6925824])
classmethod from_data(vec, time, backend=None)[source]

Create a Velocity object from velocity vector array and Time object.

Return type:

Velocity

Parameters:
  • vec (Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str])

  • time (Time)

  • backend (str | Any | None)

convert_to(backend)[source]

Convert the Velocity object to a different backend.

Return type:

Velocity

Parameters:

backend (str | Any | None)

interp(time, method='linear', check_bounds=True)[source]

Interpolate the Velocity to the given times using the specified method. Currently only ‘linear’ interpolation is supported.

Return type:

Velocity

Parameters:
  • time (Time)

  • method (str)

  • check_bounds (bool)

rotate(rot)[source]
Return type:

Velocity

Parameters:

rot (Rotation)

property acc: Acceleration

Calculate the acceleration from the velocity using central differences.

property backend: str
property magnitude: Any

Get the velocity magnitude in m/s.

time: Time
property unit: Any

Get the unit velocity vector.

vec: Any

Submodules