Transform Graph

Frame graph with BFS pathfinding and epipolar geometry

class TransformGraph

Manages a graph of coordinate frames connected by spatial transformations.

Uses an undirected NetworkX graph with directional metadata on edges.

Each edge stores:

- transform: The BaseTransform object

- parent: The source frame (defines the "natural" direction)

- is_cache: Whether this is a cached shortcut (True) or added edge (False)

- weight: 1.0 for added edges, 0.1 for cached shortcuts

Features:

- Automatic path finding and transform composition

- Lazy inversion when traversing against natural direction

- Shortcut caching for O(1) repeated queries

- Dependency-aware cache invalidation

- JSON-compatible serialization

Properties
graph
Returns the internal NetworkX graph (read-only).
frames
Returns list of all frame IDs in the graph.
edges
Returns list of all added edges as (reference_frame, target_frame) tuples.
TransformGraph.__init__
__init__(self)
classmethod TransformGraph.from_dict
from_dict(cls, data: dict[str, Any]) -> TransformGraph

Deserialize a graph from a dictionary.

Parameters
data
Dictionary produced by to_dict().
Returns

New TransformGraph instance.

TransformGraph.has_frame
has_frame(self, frame_id: str) -> bool

Check if a frame exists in the graph.

TransformGraph.has_transform
has_transform(self, source_frame: str, target_frame: str) -> bool

Check if a direct transform (edge) exists between two frames.

TransformGraph.add_transform
add_transform( self, source_frame: str, target_frame: str, transform: BaseTransform ) -> None

Add a transform between two frames.

API: add_transform(source, target, transform)

- SOURCE: The domain frame (where vectors start).

- TARGET: The codomain/reference frame (where vectors end).

- TRANSFORM: Source→Target operator.

The transform maps Source coordinates to Target coordinates:

P_target = transform * P_source

Parameters
source_frame
The source/domain frame ID.
target_frame
The target/reference frame ID.
transform
The transform from source to target.
Raises
TransformGraph.update_transform
update_transform( self, source_frame: str, target_frame: str, transform: BaseTransform ) -> None

Update an existing transform between two frames.

Automatically invalidates any cached shortcuts that depend on this edge.

Parameters
source_frame
The source frame ID.
target_frame
The target frame ID.
transform
The new transform from source to target.
Raises
TransformGraph.remove_transform
remove_transform(self, frame_a: str, frame_b: str) -> None

Remove a transform (edge) between two frames.

Parameters
frame_a
First frame ID.
frame_b
Second frame ID.
Raises
TransformGraph.get_transform
get_transform( self, source_frame: str, target_frame: str ) -> BaseTransform

Get the transform from source_frame to target_frame.

Automatically finds the shortest path and composes transforms.

Results are cached as shortcut edges for O(1) subsequent lookups.

Parameters
source_frame
The source frame ID.
target_frame
The target frame ID.
Returns

BaseTransform: The composed transform T_source_to_target.

Raises
TransformGraph.is_projection_frame
is_projection_frame(self, frame_id: str) -> bool

Check if a frame is a 2D projection frame (e.g., an Image frame).

Rule: A frame is a projection frame if ALL edges connected to it treat it

as a projection space.

- If transform maps INTO frame (frame is Target), transform must be a Projection.

- If transform maps OUT OF frame (frame is Source), transform must be an InverseProjection.

TransformGraph.get_essential_matrix
get_essential_matrix(self, image_frame_1: str, image_frame_2: str) -> np.ndarray

Compute the Essential Matrix E between two image frames.

E = [t]_x R

where R, t describe method to transform points from Camera 1 to Camera 2.

X2 = R X1 + t.

TransformGraph.get_fundamental_matrix
get_fundamental_matrix(self, image_frame_1: str, image_frame_2: str) -> np.ndarray

Compute the Fundamental Matrix F between two image frames.

F = K2^-T E K1^-1

x2^T F x1 = 0

TransformGraph.get_homography
get_homography( self, image_frame_1: str, image_frame_2: str, plane_normal: np.ndarray, plane_distance: float ) -> np.ndarray

Compute Homography H mapping pixels from image 1 to image 2 induced by a plane.

x2 ~ H x1

Plane equation in Camera 1 frame: n^T X = d

H = K2 (R + t n^T / d) K1^-1

Parameters
image_frame_1
Source image frame.
image_frame_2
Target image frame.
plane_normal
Normal vector of the plane in Camera 1 frame (3,).
plane_distance
Distance to the plane in Camera 1 frame (scalar).
TransformGraph.estimate_skew
estimate_skew(intrinsic_matrix: np.ndarray) -> float

Estimate the skew parameter from an intrinsic matrix K.

K = [[fx, s, cx], [0, fy, cy], [0, 0, 1]]

Returns s.

TransformGraph.clear_cache
clear_cache(self) -> None

Clear all cached shortcut transforms.

Removes edges marked with is_cache=True.

TransformGraph.get_connected_components
get_connected_components(self) -> list[set[str]]

Get all connected components in the graph.

Returns

List of sets, where each set contains frame IDs of a connected component.

TransformGraph.get_connected_nodes
get_connected_nodes(self, frame_id: str) -> set[str]

Get the set of all nodes connected to the given frame (its connected component).

Parameters
frame_id
The frame to start searching from.
Returns

Set of connected frame IDs.

Raises
TransformGraph.to_dict
to_dict(self) -> dict[str, Any]

Serialize the entire graph to a dictionary.

Returns

Dict containing 'frames' and 'edges' (only explicit, non-cached edges). Frame IDs that are not JSON-native (tuples, datetime, UUID) are encoded as tagged dicts with `__type__ and value keys so they survive json.dumps/json.loads` roundtrip without information loss.