rerun-io / rerun

Visualize streams of multimodal data. Free, fast, easy to use, and simple to integrate. Built in Rust.
https://rerun.io/
Apache License 2.0
7.02k stars 351 forks source link

Support for "Derived" Transforms #1533

Open jleibs opened 1 year ago

jleibs commented 1 year ago

Issue Overview

In the current design, the transform graph must perfectly match the entity path hierarchy.

However, there are situations where the user-data graph transform representation does not match the path hierarchy we want to use for logging data. For example, a URDF file may contain several linkages that logically exist to match the mechanical design of the robot, but are otherwise not interesting to the user.

At present the user must choose between two sub-optimal options:

Proposal: "Derived" transforms.

The idea is that the value of the transform for that link in the path-hierarchy, rather than being directly fetched from the store, would be derived from some other function at render time. Specifically, this must be a function of time (along with some other data logged to the store). However, the high-level transform semantics are crucially unchanged: the transform (regardless of how its derived) still represents the transform between those two spaces defined by the path hierarchy and is considered fixed for a given instant in time. As such, cross-space projection can be totally independent of of the resolver -- it just needs to know that at a given timepoint on a given timeline, the transform was derived as having a particular value. Additionally the means of derivation need not play a role in space-partitioning and view-construction perspective. The mere fact that a "derived transform" has been logged to a path allows us to do all of the same path-hierarchy analysis we already perform.

The current transforms already fit into this model. They simply use a "default resolver" that returns the latest-at value of the transform logged to that path.

There are two immediate mechanisms of derived transforms that come to mind as being useful...

"graph-lookup" transform

First, raw transforms would be logged with a graph-centric representation.

For example, adopting from a ROS tf system, transforms would be logged to a path derived from their source frame, (e.g. tf/<child_frame_id>) and include a link to their dest frame (e.g. tf/<parent_frame_id>).

rr.log_rigid3_graph_link(source_frame="tf/camera_optical_rgb", dest_frame="tf/camera_link", dest_from_source=...)
rr.log_rigid3_graph_link(source_frame="tf/camera_link", dest_frame="tf/left_wrist_link", dest_from_source=...)
rr.log_rigid3_graph_link(source_frame="tf/left_wrist_link", dest_frame="tf/left_arm_link", dest_from_source=...)
rr.log_rigid3_graph_link(source_frame="tf/left_arm_link", dest_frame="tf/base_link", dest_from_source=...)

Then, a given path can be logged as a derived "graph lookup" transform:

rr.log_rigid3_graph_lookup('robot/camera', parent='tf/base_link', child='tf/camera_optical_rgb')

At resolution time, this would then do a graph walk based on the logged topology.

Parametric transform

Much simpler than graph-based resolution is simple support for parametric transforms.

Once we have derived transforms, we can derive transforms from things like scalar values representing joint rotations or linear actuator offsets.

Exa:

rr.log_scalar('joint/arm', arm_rotation_rad)
rr.log_rigid3d_parametric_rotation("robot/arm", axis=(1,0,0), angle="joint/arm")

This avoids the need for something like the "robot state publisher" in ROS.

nikolausWest commented 10 months ago

The parametric transform could potentially be done using some kind of entity reference.

rr.log('joint/arm', rr.components.Angle(radians=arm_rotation_rad))
rr.log("robot/arm", rr.Transform3D(axis=(1,0,0), angle=rr.components.Angle.Reference('joint/arm')))