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.
Constructors
TransformGraph.__init____init__(self)
TransformGraph.from_dictfrom_dict(cls, data: dict[str, Any]) -> TransformGraph
Deserialize a graph from a dictionary.
Parameters
- data
- Dictionary produced by to_dict().
Returns
New TransformGraph instance.
Methods
TransformGraph.has_framehas_frame(self, frame_id: str) -> bool
Check if a frame exists in the graph.
TransformGraph.has_transformhas_transform(self, source_frame: str, target_frame: str) -> bool
Check if a direct transform (edge) exists between two frames.
TransformGraph.add_transformadd_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_transformupdate_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_transformremove_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_transformget_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_frameis_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_matrixget_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_matrixget_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_homographyget_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_skewestimate_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_cacheclear_cache(self) -> None
Clear all cached shortcut transforms.
Removes edges marked with is_cache=True.
TransformGraph.get_connected_componentsget_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_nodesget_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_dictto_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.