astrix.spatial package¶
- class Frame(rot, loc=None, ref_frame=None, backend=None, name='unnamed-frame')[source]¶
Bases:
objectA 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.primitives import Frame, Point >>> from scipy.spatial.transform import Rotation >>> >>> rot = Rotation.from_euler( ... "xyz", [90, 0, 0], degrees=True ... ) # 90 degree rotation about x-axis >>> loc = Point.from_geodet([27.47, 153.03, 0]) # Brisbane location >>> frame_static = Frame(rot, loc) # Frame with static rotation and location
>>> frame_static.interp_rot().as_euler("xyz", degrees=True) # Get absolute rotation array([[90., 0., 0.]]) >>> frame_static.loc.geodet # Get frame location in geodetic coordinates array([[153.03, 27.47, 0.0]])
Time-varying frame with rotation sequence and static location:
>>> from astrix.primitives import Time >>> 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) >>> loc = Point.from_geodet([27.47, 153.03, 0]) # Brisbane location >>> frame_dynamic_rot = Frame( ... rot_seq, loc ... ) # Frame with time-varying rotation and static location
>>> interp_rot = frame_dynamic_rot.interp_rot( ... 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 absolute rotation as Euler angles array([[45., 0., 0.]]) >>> frame_dynamic_rot.loc.geodet # Get frame location in geodetic coordinates array([[153.03, 27.47, 0.0]])
Frame defined relative to another frame:
>>> rot_ref = Rotation.from_euler( ... "xyz", [0, 30, 0], degrees=True ... ) # Reference frame >>> frame_ref = Frame(rot_ref, loc) # Reference frame >>> 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 ... ) # Absolute rotation (rot_ref * rot_rel) array([[ 0., 70., 0.]]) >>> frame.loc.geodet # (Same as reference frame) array([[153.03, 27.47, 0.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:
- 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 location and rotation indeces align. Prefer interp_rot for general use.
- Return type:
- 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=TimeInvariant object, check_bounds=True)[source]¶
Get the interpolated location of the frame at the given times. If the location is static, time can be None.
- interp_rot(time=TimeInvariant object, 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 (TimeLike)
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.
- 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:
- 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
- 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 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.
- class Path(point, backend=None)[source]¶
-
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:
Examples
Instantiating a Path from a list of Points:
>>> from astrix.primitives 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]), ... ] ... ) # Somewhere very hot in the middle of the Earth
Interpolate the Path to a new time and get velocity:
>>> path.interp( Time.from_datetime(datetime(2025, 1, 1, 12, 0, 1, 500000, tzinfo=timezone.utc)), method="linear" ).ecef # Interpolate to halfway between second and third point, return ECEF array array([[2.5, 4.9, 0.7]]) >>> vel = path.interp_vel( Time.from_datetime(datetime(2025, 1, 1, 12, 0, 1, 500000, tzinfo=timezone.utc)), ) >>> vel.magnitude # Interpolated velocity magnitude in m/s array([2.48997992]) >>> vel.unit # Interpolated unit velocity vector array([[0.40160966, 0.88354126, 0.2409658 ]])
- convert_to(backend)[source]¶
Convert the Path object to a different backend.
- Return type:
- 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:
- 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:
- interp_vel(time, method='linear', check_bounds=True)[source]¶
Interpolate the Path velocity to the given times using the specified method. Currently only ‘linear’ interpolation is supported.
- 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:
- 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.
- property is_singular: bool¶
Check if the Path object represents a single point. Always False for Path objects.
- class Point(ecef, time=TimeInvariant object, backend=None)[source]¶
-
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 (TimeLike, 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.primitives import Point >>> p1 = Point( ... [-5047162.4, 2568329.79, -2924521.17] ... ) # ECEF coordinates of Brisbane in metres >>> p1.geodet # Convert to geodetic coordinates (lat, lon, alt) array([[-27.47, 153.03, 0.0]]) >>> p2 = Point.from_geodet([-27.47, 153.03, 0]) # lat, lon in degrees, alt in metres >>> p2.ecef # Convert back to ECEF coordinates 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 ... [3877000.0, 350000.0, 5027000.0], # Somewhere else ... ] ... ) >>> pt_bris = pts[0] # First point (Brisbane) >>> assert len(pts) == 3
Dynamic point with time:
>>> from datetime import datetime, timezone >>> from astrix.primitives 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), ... datetime(2021, 1, 1, 14, 0, 0, tzinfo=timezone.utc), ... ] ... ) >>> pts_time = Point( ... [ ... [-5047162.4, 2568329.79, -2924521.17], # Brisbane ... [-2694045.0, -4293642.0, 3857878.0], # San Francisco ... [3877000.0, 350000.0, 5027000.0], # Somewhere else ... ], ... time=times, ... ) >>> pts.has_time True >>> pts.is_singular False >>> pts_new = pts + Point( ... [[-1000, -1000, -1000]], ... time=Time.from_datetime( ... datetime(2021, 1, 1, 15, 0, 0, tzinfo=timezone.utc) ... ), ... ) >>> assert len(pts_new) == 4
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=TimeInvariant object, backend=None)[source]¶
Create a Point object from geodetic coordinates (lat, lon, alt). Lat and lon are in degrees, alt is in meters.
- convert_to(backend)[source]¶
Convert the Point object to a different backend.
- Return type:
- 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=(TimeInvariant object, TimeInvariant object), backend=numpy), time=TimeInvariant object, check=True, backend=None)[source]¶
Bases:
objectA 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.
backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.
check (bool)
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
TBC
- classmethod from_az_el(az_el, frame=Frame(name=ECEF, static_rot=True, static_loc=True, has_ref=False, time_bounds=(TimeInvariant object, TimeInvariant object), backend=numpy), time=TimeInvariant object, 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:
- 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:
- classmethod from_points(endpoint, origin, time=TimeInvariant object, check=True, backend=None)[source]¶
Create a Ray object from origin and endpoint arrays in ECEF frame.
- Parameters:
origin (Point) – Origin points (ECEF coordinates). Must be length N or 1.
endpoint (Point) – End points (ECEF coordinates). Must be length N.
time (Time, optional) – Time object associated with the rays. Must be length N or 1. Defaults to TIME_INVARIANT (no time dependency).
backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.
check (bool)
- Returns:
Ray object defined by the origin and direction from origin to endpoint.
- Return type:
Notes
Origin and endpoint Point o
- 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:
- Returns:
Ray object defined by the frame origin and direction to the target point(s).
- Return type:
- convert_to(backend)[source]¶
Convert the Ray object to a different backend.
- Return type:
- Parameters:
backend (str | Any | None)
- interp(time, check_bounds=True)[source]¶
Interpolate the Ray origin and direction to the given times.
- 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:
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.
- property az_el¶
Return the heading (from north) and elevation (from horizontal) angles in degrees.
- property backend: str¶
Get the name of the array backend in use (e.g., ‘numpy’, ‘jax’).
- 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 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 RotationSequence(rot, time, backend=None)[source]¶
Bases:
RotationLikeA 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.primitives 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:
- 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:
- 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.
Submodules¶
- astrix.spatial.frame module
- astrix.spatial.location module
- astrix.spatial.ray module
RayRay.from_az_el()Ray.from_camera()Ray.from_points()Ray.from_target_frame()Ray.convert_to()Ray.interp()Ray.project_to_cam()Ray.replace_frame()Ray.to_ecef()Ray.to_frame()Ray.to_ned()Ray.az_elRay.backendRay.frameRay.has_timeRay.origin_pointsRay.origin_relRay.timeRay.total_angleRay.unit_ecefRay.unit_rel
- astrix.spatial.rotation module