Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,39 @@ EVA episode extrinsics store one `base_T_cam` matrix for each arm. Human
episodes store the head pose as `world_T_head`. The head frame and the camera
frame are the same frame for egocentric human data.

### The calibration block

An episode written after the `calibration` attribute existed names one
reference frame and expresses every pose in it.

```text
calibration.reference_frame robot_base | slam_world | camera:<name>
calibration.cameras[c].ref_T_cam camera c's pose in the reference frame
calibration.arm_bases[side] ref_T_armbase, the arm base pose in it
```

The camera that defines the reference frame needs no `ref_T_cam`. It is the
identity by definition.

Each camera also declares its projection model and distortion coefficients.

```text
calibration.cameras[c].model PINHOLE | OPENCV | KANNALA_BRANDT
calibration.cameras[c].distortion coefficients in that model's order
calibration.cameras[c].rectified whether the stored frames are rectified
```

A camera that declares no model is `PINHOLE` with no coefficients. No
projection site honors a non-pinhole model yet. Collect the declaration anyway:
it measures a vendor's rig, and a rig that has moved cannot be recalibrated
after the fact.

`Calibration.base_T_cam(side)` composes the two and returns what the EVA
transform pipeline consumes. An episode that predates the block reaches the
same value through the shim in `egomimic/rldb/zarr/calibration.py`: its
reference frame is `camera:front_1`, so `arm_bases[side]` is the inverse of
the stored `extrinsics[side]`.

### Existing episodes need no migration

The `ref_T_cam` naming records the direction the code already used. It changes
Expand Down
30 changes: 30 additions & 0 deletions egomimic/rldb/embodiment/embodiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,36 @@ def action_space(self) -> str:
)
return spaces.pop()

@property
def arity(self) -> str | None:
"""Return the arity suffix encoded in ``embodiment_name``.

Returns:
The text after ``<platform embodiment_prefix>_``, or ``None`` when
the resolved value has no matching embodiment name.
"""
if self.embodiment_name is None:
return None
prefix = f"{self.platform.embodiment_prefix}_"
if self.embodiment_name.startswith(prefix):
return self.embodiment_name[len(prefix) :]
return None

@property
def sides(self) -> tuple[str, ...]:
"""Return side candidates implied by the encoded arity.

``left_arm`` and ``right_arm`` select one side. All other values return
both candidates; callers resolving a morphology mapping must still
filter candidates not present in ``end_effectors``.
"""
arity = self.arity
if arity == "left_arm":
return ("left",)
if arity == "right_arm":
return ("right",)
return SIDES

@property
def embodiment_class(self) -> type["Embodiment"]:
"""Import and return the platform's configured ``Embodiment`` class.
Expand Down
10 changes: 10 additions & 0 deletions egomimic/rldb/embodiment/eva.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@


class Eva(Embodiment):
"""Dataset transforms and visualization for the EVA X5 platform.

``EXTRINSICS`` is used only when a sample has no episode-specific
``base_T_cam`` poses. ``INTRINSICS`` is the visualization fallback when a
sample has no camera matrix. Both constants describe the original EVA rig.
"""

INTRINSICS = ARIA_INTRINSICS
#: Compatibility fallback: one ``base_T_cam`` pose per arm.
EXTRINSICS = {
"left": np.array(
[
Expand Down Expand Up @@ -248,6 +256,7 @@ def _build_eva_bimanual_eef_frame_transform_list(
) -> list[Transform]:
"""EVA bimanual transform pipeline with actions expressed relative to the
current EEF pose (wrist frame), analogous to keypoints relative to wrist pose."""
# Supply class-default poses only for sample keys absent at transform time.
extrinsics = Eva.EXTRINSICS
left_base_T_cam_pose = _matrix_to_xyzwxyz(extrinsics["left"][None, :])[0]
right_base_T_cam_pose = _matrix_to_xyzwxyz(extrinsics["right"][None, :])[0]
Expand Down Expand Up @@ -411,6 +420,7 @@ def _build_eva_bimanual_transform_list(
is_quat: bool = True,
) -> list[Transform]:
"""Canonical EVA bimanual transform pipeline used by tests and notebooks."""
# Supply class-default poses only for sample keys absent at transform time.
extrinsics = Eva.EXTRINSICS
left_base_T_cam_pose = _matrix_to_xyzwxyz(extrinsics["left"][None, :])[0]
right_base_T_cam_pose = _matrix_to_xyzwxyz(extrinsics["right"][None, :])[0]
Expand Down
20 changes: 17 additions & 3 deletions egomimic/rldb/zarr/action_chunk_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@
xyzw_to_wxyz,
)


def base_T_cam_pose_key(side: str) -> str:
"""Return the sample key for ``base_T_cam`` as ``[xyz, qw, qx, qy, qz]``."""
return f"{side}_base_T_cam_pose"


def _apply_fallbacks(batch, extra_batch_key) -> None:
"""Add fallback values for absent keys without replacing sample values."""
for key, value in (extra_batch_key or {}).items():
batch.setdefault(key, value)


# ---------------------------------------------------------------------------
# Base Transform
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -159,7 +171,9 @@ def __init__(
frame. This pose represents ``reference_T_target``.
chunk_world: Batch key for the input poses.
transformed_key_name: Batch key for the output poses.
extra_batch_key: Values to add to the batch before the transform.
extra_batch_key: Fallback values for keys the batch does not
already carry. A per-episode value in the batch wins, so a
class constant here is a default and not an override.
mode: Input and output layout. Use ``"xyz"``, ``"xyzypr"``, or
``"xyzwxyz"``.
inverse: If true, compute ``target_T_chunk`` from
Expand Down Expand Up @@ -190,7 +204,7 @@ def transform(self, batch):
ValueError: If ``mode`` is not a supported pose layout.
"""
# Flatten the leading chunk dimensions into one pose dimension.
batch.update(self.extra_batch_key or {})
_apply_fallbacks(batch, self.extra_batch_key)
target_world_pose = np.asarray(batch[self.target_world])
chunk_world_poses = np.asarray(batch[self.chunk_world])
chunk_world_poses_shape = None
Expand Down Expand Up @@ -437,7 +451,7 @@ def transform(self, batch):
returns
batch with new key containing transformed chunk world in target frame: (T, 14)
"""
batch.update(self.extra_batch_key or {})
_apply_fallbacks(batch, self.extra_batch_key)
left_target_world = batch[self.left_target_world]
right_target_world = batch[self.right_target_world]
chunk_world = batch[self.chunk_world]
Expand Down
Loading
Loading