Semantic data annotation#
This guide covers how to add semantic metadata to tensors using
TensorSemantics. Semantic annotations describe what
a tensor represents (e.g. joint positions, target torques) and provide
element-level naming, making the generated YAML specifications
self-documenting and enabling downstream consumers to interpret the data
correctly.
Note
Semantic annotation is only available for
input_tensors() and
output_tensors(). It is not supported for
method() nodes.
Basic usage#
Instead of passing raw tensors, wrap them in TensorSemantics objects.
Pass them as a single object or as a list:
import torch
import leapp
from leapp import annotate, TensorSemantics
from leapp.utils.enums import InputKindEnum, OutputKindEnum
joint_pos = torch.randn(1, 12)
joint_vel = torch.randn(1, 12)
leapp.start("my_robot")
traced_pos, traced_vel = annotate.input_tensors("policy", [
TensorSemantics("joint_pos", joint_pos,
kind=InputKindEnum.JOINT_POSITION,
element_names=["hip_l", "knee_l", "ankle_l",
"hip_r", "knee_r", "ankle_r",
"shoulder_l", "elbow_l", "wrist_l",
"shoulder_r", "elbow_r", "wrist_r"]),
TensorSemantics("joint_vel", joint_vel,
kind=InputKindEnum.JOINT_VELOCITY),
])
command = traced_pos + traced_vel
annotate.output_tensors("policy", [
TensorSemantics("command", command,
kind=OutputKindEnum.JOINT_TORQUES,
element_names=["hip_l", "knee_l", "ankle_l",
"hip_r", "knee_r", "ankle_r",
"shoulder_l", "elbow_l", "wrist_l",
"shoulder_r", "elbow_r", "wrist_r"]),
])
leapp.stop()
leapp.compile_graph()
The generated YAML includes the semantic metadata:
models:
policy:
inputs:
- name: joint_pos
dtype: float32
shape: [1, 12]
type: tensor
kind: state/joint/position
element_names: [[hip_l, knee_l, ankle_l, hip_r, knee_r, ankle_r,
shoulder_l, elbow_l, wrist_l, shoulder_r, elbow_r, wrist_r]]
- name: joint_vel
dtype: float32
shape: [1, 12]
type: tensor
kind: state/joint/velocity
outputs:
- name: command
dtype: float32
shape: [1, 12]
type: tensor
kind: target/joint/torques
element_names: [[hip_l, knee_l, ankle_l, hip_r, knee_r, ankle_r,
shoulder_l, elbow_l, wrist_l, shoulder_r, elbow_r, wrist_r]]
Semantic fields#
kind#
The kind field describes the semantic role of a tensor — what
physical quantity or command it represents. LEAPP provides two separate
enums for inputs and outputs to clearly distinguish between observed
state and commanded targets. kind may also be any plain string when
you need a custom semantic label.
InputKindEnum#
Used with input_tensors(). These represent
observed state or commanded references flowing into a node.
Enum value |
YAML string |
Description |
|---|---|---|
|
|
Observed joint positions (e.g. encoder readings) |
|
|
Observed joint velocities |
|
|
Observed joint effort |
|
|
Observed body pose |
|
|
Observed body position |
|
|
Observed body velocity |
|
|
Observed body acceleration |
|
|
Body linear acceleration (e.g. from IMU) |
|
|
Body linear velocity |
|
|
Body angular acceleration |
|
|
Body angular velocity (e.g. gyroscope) |
|
|
Body rotation / orientation |
|
|
Observed wrench values |
|
|
Generic 3D vector state |
|
|
Commanded joint position reference |
|
|
Commanded joint velocity reference |
|
|
Commanded body rotation reference |
|
|
Commanded body velocity reference |
|
|
Commanded body pose reference |
|
|
Commanded joint torques reference |
from leapp.utils.enums import InputKindEnum
TensorSemantics("joint_pos", tensor, kind=InputKindEnum.JOINT_POSITION)
TensorSemantics("imu_gyro", tensor,
kind=InputKindEnum.BODY_ANGULAR_VELOCITY)
TensorSemantics("target_pos", tensor,
kind=InputKindEnum.COMMAND_JOINT_POSITION)
# Custom string kinds are also allowed.
TensorSemantics("terrain_latent", tensor,
kind="state/environment/terrain_embedding")
OutputKindEnum#
Used with output_tensors(). These represent
target commands or control outputs produced by a node.
Enum value |
YAML string |
Description |
|---|---|---|
|
|
Proportional gain |
|
|
Derivative gain |
|
|
Target joint position |
|
|
Target joint velocity |
|
|
Target joint torques |
|
|
Target joint effort |
|
|
Target body position |
|
|
Target body linear acceleration |
|
|
Target body orientation |
|
|
Target body linear velocity |
|
|
Target body angular acceleration |
from leapp.utils.enums import OutputKindEnum
TensorSemantics("torques", action, kind=OutputKindEnum.JOINT_TORQUES)
TensorSemantics("kp_gains", kp, kind=OutputKindEnum.KP)
Note
LEAPP does not enforce using InputKindEnum only for inputs or
OutputKindEnum only for outputs, but it is strongly recommended to
follow this convention. The kind field accepts enum values and
plain strings.
element_names#
The element_names field provides human-readable names for the
elements along each dimension of a tensor. The canonical format is
list[list[str]], where the outer list corresponds to tensor
dimensions and each inner list names the elements in that dimension.
LEAPP also accepts several shorthand formats and normalizes them
automatically:
Input format |
Normalized to |
Use case |
|---|---|---|
|
|
Single named element |
|
|
Flat list — names for one dimension |
|
|
Already canonical — per-dimension names |
|
|
Partial — only name specific dimensions |
# Name elements along the last dimension.
TensorSemantics(
"joint_pos", tensor,
element_names=["hip", "knee", "ankle",
"shoulder", "elbow", "wrist"])
# Name elements per dimension (e.g. for a [batch, 3] tensor).
TensorSemantics(
"position", tensor,
element_names=[None, ["x", "y", "z"]])
# Name a single element.
TensorSemantics("gravity", tensor, element_names="z")
extra#
The extra field accepts a dictionary of additional semantic metadata.
Keys in extra are flattened into the generated YAML tensor entry rather
than nested under an extra key. Use this for downstream-specific fields
that LEAPP does not model directly, such as coordinate frames, external IDs,
units, or application-specific labels.
TensorSemantics(
"joint_pos",
tensor,
kind=InputKindEnum.JOINT_POSITION,
extra={"frame": "base", "units": "rad"},
)
The generated YAML includes the extra fields at the same level as kind and
element_names:
- name: joint_pos
dtype: float32
shape: [1, 12]
type: tensor
kind: state/joint/position
frame: base
units: rad
Unknown semantic keys applied internally through update_semantics() are
also stored in extra and serialized this way.
Warning
extra fields are non-standard, project-defined metadata. Downstream
deployment frameworks should not be expected to understand or support them.
Use extra only for project-specific integration data, and prefer
standard LEAPP semantic fields whenever possible.
Passing conventions#
TensorSemantics are passed as a single object or a list. They
cannot be placed inside a dict — use the standard dict format for raw
tensors and the list format for TensorSemantics. The only supported
top-level formats are:
a dict of named raw tensors
a single
TensorSemanticsa list of
TensorSemantics
Bare top-level tensors and other unnamed top-level collections are not supported.
# OK: single TensorSemantics
annotate.input_tensors(
"node",
TensorSemantics("pos", tensor, kind=InputKindEnum.JOINT_POSITION),
)
# OK: list of TensorSemantics
annotate.input_tensors("node", [
TensorSemantics("pos", pos_tensor,
kind=InputKindEnum.JOINT_POSITION),
TensorSemantics("vel", vel_tensor,
kind=InputKindEnum.JOINT_VELOCITY),
])
# OK: regular dict (no semantic metadata)
annotate.input_tensors("node", {"pos": pos_tensor, "vel": vel_tensor})
# NOT supported: TensorSemantics inside a dict
annotate.input_tensors("node", {
"pos": TensorSemantics("pos", pos_tensor,
kind=InputKindEnum.JOINT_POSITION),
})
# NOT supported: mixing TensorSemantics and raw tensors
# (use multiple calls to input_tensors in that case)
annotate.input_tensors("node", [
TensorSemantics("pos", pos_tensor,
kind=InputKindEnum.JOINT_POSITION),
vel_tensor,
])
Limitations#
input_tensorsandoutput_tensorsonly — semantic annotations are not available formethod()nodes. These nodes derive their I/O descriptions automatically from function signatures and traced values.No mixing — when using
TensorSemantics, all items must beTensorSemantics. You cannot mix raw tensors andTensorSemanticsin the same list.No dict wrapping —
TensorSemanticsmust be passed directly or in a list. Each carries its own name, so the dict key is unnecessary.Name uniqueness — each
TensorSemanticsname must be unique within the same node’s inputs (or outputs). Duplicate names raise an error.Semantic fields are optional — all semantic fields (
kind,element_names, andextra) are optional. ATensorSemanticswith no semantic fields behaves identically to passing a raw tensor with the same name.Extra fields are flattened — keys in
extrabecome top-level YAML fields on the tensor entry. Avoid keys that collide with built-in fields such asname,dtype,shape,type,kind, orelement_names.