From 5dd0515fd9c7aae83392c685b7e241438e383f4d Mon Sep 17 00:00:00 2001 From: Andrei Aristarkhov Date: Thu, 30 Jul 2026 14:25:25 -0700 Subject: [PATCH 1/2] refactor(retargeting): rename HeadPose tensor type to HeadInput The DeviceIO source tensor types are named after the input they carry -- HandInput, ControllerInput, FullBodyInput, Generic3AxisPedalInput -- with HeadPose the lone exception. Rename it, and HeadPoseIndex alongside it, so the set is consistent. HeadPose and HeadPoseIndex stay available as deprecated aliases through the module's existing __getattr__, so graphs referring to them keep working and warn rather than break. Only the tensor group type moves: the FlatBuffers table in head.fbs is still called HeadPose, and the group's fields are unchanged. Signed-off-by: Andrei Aristarkhov --- .../python/deviceio_viser.py | 8 +-- .../retargeting/python/sources_example.py | 8 +-- examples/teleop_ros2/python/messages.py | 6 +- .../python/tensor_group_helpers.py | 4 +- .../teleop_ros2/python/tests/test_messages.py | 14 ++-- .../python/teleop_controls_simple_helper.py | 8 +-- .../python/test_sources.py | 12 ++-- .../python/test_transforms.py | 70 +++++++++---------- .../python/transform_numpy_version_smoke.py | 14 ++-- .../deviceio_source_nodes/head_source.py | 20 +++--- .../tensor_types/__init__.py | 12 ++-- .../tensor_types/indices.py | 6 +- .../tensor_types/standard_types.py | 2 +- .../utilities/head_transform.py | 14 ++-- 14 files changed, 101 insertions(+), 97 deletions(-) diff --git a/examples/deviceio_live_view/python/deviceio_viser.py b/examples/deviceio_live_view/python/deviceio_viser.py index 7f612a840..13effd8c8 100644 --- a/examples/deviceio_live_view/python/deviceio_viser.py +++ b/examples/deviceio_live_view/python/deviceio_viser.py @@ -26,7 +26,7 @@ BodyJointIndex, ControllerInputIndex, FullBodyInputIndex, - HeadPoseIndex, + HeadInputIndex, ) HANDS_CHANNEL = "hands" @@ -425,12 +425,12 @@ def __init__( ) def update_if_active(self, head) -> bool: - if head.is_none or not bool(head[HeadPoseIndex.IS_VALID]): + if head.is_none or not bool(head[HeadInputIndex.IS_VALID]): self.frame.visible = False return False - position = np.asarray(head[HeadPoseIndex.POSITION], dtype=np.float32) - orientation = np.asarray(head[HeadPoseIndex.ORIENTATION], dtype=np.float32) + position = np.asarray(head[HeadInputIndex.POSITION], dtype=np.float32) + orientation = np.asarray(head[HeadInputIndex.ORIENTATION], dtype=np.float32) self.frame.position = ( float(position[0]), float(position[1]), diff --git a/examples/retargeting/python/sources_example.py b/examples/retargeting/python/sources_example.py index aac813b09..848d48d3a 100755 --- a/examples/retargeting/python/sources_example.py +++ b/examples/retargeting/python/sources_example.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """ @@ -34,7 +34,7 @@ from isaacteleop.retargeting_engine.interface import OutputCombiner, TensorGroup from isaacteleop.retargeting_engine.tensor_types import ( HandInputIndex, - HeadPoseIndex, + HeadInputIndex, ControllerInputIndex, ) @@ -226,10 +226,10 @@ def main(): if head.is_none: print(" Status: ABSENT (no tracker)") else: - head_valid = head[HeadPoseIndex.IS_VALID] + head_valid = head[HeadInputIndex.IS_VALID] print(f" Status: {'VALID' if head_valid else 'INVALID'}") if head_valid: - head_position = head[HeadPoseIndex.POSITION] + head_position = head[HeadInputIndex.POSITION] print( f" Position: [{head_position[0]:6.3f}, {head_position[1]:6.3f}, {head_position[2]:6.3f}]" ) diff --git a/examples/teleop_ros2/python/messages.py b/examples/teleop_ros2/python/messages.py index a586f1ba0..11c5f094a 100644 --- a/examples/teleop_ros2/python/messages.py +++ b/examples/teleop_ros2/python/messages.py @@ -22,7 +22,7 @@ FullBodyInputIndex, HandInputIndex, HandJointIndex, - HeadPoseIndex, + HeadInputIndex, ) from constants import BODY_JOINT_NAMES, HAND_POSE_JOINT_INDICES, HAND_POSE_NAMES @@ -403,8 +403,8 @@ def build_head_output( if not head_is_valid(head): return None - position = [float(x) for x in head[HeadPoseIndex.POSITION]] - orientation = [float(x) for x in head[HeadPoseIndex.ORIENTATION]] + position = [float(x) for x in head[HeadInputIndex.POSITION]] + orientation = [float(x) for x in head[HeadInputIndex.ORIENTATION]] pose = to_pose(position, orientation) if transform_rot is not None or transform_trans is not None: pose = apply_transform_to_pose(pose, transform_rot, transform_trans) diff --git a/examples/teleop_ros2/python/tensor_group_helpers.py b/examples/teleop_ros2/python/tensor_group_helpers.py index 6ad1ac213..404854c9a 100644 --- a/examples/teleop_ros2/python/tensor_group_helpers.py +++ b/examples/teleop_ros2/python/tensor_group_helpers.py @@ -9,7 +9,7 @@ ControllerInputIndex, HandInputIndex, HandJointIndex, - HeadPoseIndex, + HeadInputIndex, ) @@ -31,7 +31,7 @@ def hand_wrist_is_valid(hand: OptionalTensorGroup) -> bool: def head_is_valid(head: OptionalTensorGroup) -> bool: - return _flag_is_valid(head, HeadPoseIndex.IS_VALID) + return _flag_is_valid(head, HeadInputIndex.IS_VALID) def joint_names_from_group_type(group_type) -> list[str]: diff --git a/examples/teleop_ros2/python/tests/test_messages.py b/examples/teleop_ros2/python/tests/test_messages.py index 86f666a33..52ad7e777 100644 --- a/examples/teleop_ros2/python/tests/test_messages.py +++ b/examples/teleop_ros2/python/tests/test_messages.py @@ -26,8 +26,8 @@ HandInput, HandInputIndex, HandJointIndex, - HeadPose, - HeadPoseIndex, + HeadInput, + HeadInputIndex, RobotHandJoints, ) @@ -102,10 +102,10 @@ def _active_hand() -> TensorGroup: def _active_head() -> TensorGroup: - head = TensorGroup(HeadPose()) - head[HeadPoseIndex.POSITION] = np.array([1.0, 2.0, 3.0], dtype=np.float32) - head[HeadPoseIndex.ORIENTATION] = np.array([0.0, 0.0, 0.0, 1.0], dtype=np.float32) - head[HeadPoseIndex.IS_VALID] = True + head = TensorGroup(HeadInput()) + head[HeadInputIndex.POSITION] = np.array([1.0, 2.0, 3.0], dtype=np.float32) + head[HeadInputIndex.ORIENTATION] = np.array([0.0, 0.0, 0.0, 1.0], dtype=np.float32) + head[HeadInputIndex.IS_VALID] = True return head @@ -342,7 +342,7 @@ def test_build_head_output_keeps_pose_and_tf_consistent() -> None: def test_build_head_output_returns_none_when_head_is_absent() -> None: assert ( build_head_output( - OptionalTensorGroup(HeadPose()), + OptionalTensorGroup(HeadInput()), Time(), "world", "head", diff --git a/examples/teleop_session_manager/python/teleop_controls_simple_helper.py b/examples/teleop_session_manager/python/teleop_controls_simple_helper.py index 3915a81f3..bf087af48 100644 --- a/examples/teleop_session_manager/python/teleop_controls_simple_helper.py +++ b/examples/teleop_session_manager/python/teleop_controls_simple_helper.py @@ -26,8 +26,8 @@ ControllerInput, HandInput, HandInputIndex, - HeadPose, - HeadPoseIndex, + HeadInput, + HeadInputIndex, ) @@ -41,7 +41,7 @@ def __init__(self, name: str, print_every_n_frames: int = 30) -> None: def input_spec(self) -> RetargeterIOType: return { - "head": OptionalType(HeadPose()), + "head": OptionalType(HeadInput()), "hand_left": OptionalType(HandInput()), "hand_right": OptionalType(HandInput()), "controller_left": OptionalType(ControllerInput()), @@ -115,7 +115,7 @@ def print_frame(outputs, elapsed: float) -> None: head_pos_str = "absent" if head_present: - p = outputs["head"][HeadPoseIndex.POSITION] + p = outputs["head"][HeadInputIndex.POSITION] head_pos_str = f"[{p[0]:+.3f}, {p[1]:+.3f}, {p[2]:+.3f}]" left_wrist_str = "absent" diff --git a/src/core/retargeting_engine_tests/python/test_sources.py b/src/core/retargeting_engine_tests/python/test_sources.py index 29748c662..a3e1eaa96 100644 --- a/src/core/retargeting_engine_tests/python/test_sources.py +++ b/src/core/retargeting_engine_tests/python/test_sources.py @@ -23,7 +23,7 @@ from isaacteleop.retargeting_engine.interface.base_retargeter import _make_output_group from isaacteleop.retargeting_engine.tensor_types import ( ControllerInputIndex, - HeadPoseIndex, + HeadInputIndex, ) from isaacteleop.schema import ( Point, @@ -260,12 +260,12 @@ def test_head_source_compute_active(self): head = outputs["head"] assert not head.is_none np.testing.assert_array_almost_equal( - head[HeadPoseIndex.POSITION], [1.0, 2.0, 3.0] + head[HeadInputIndex.POSITION], [1.0, 2.0, 3.0] ) np.testing.assert_array_almost_equal( - head[HeadPoseIndex.ORIENTATION], [0.0, 0.0, 0.0, 1.0] + head[HeadInputIndex.ORIENTATION], [0.0, 0.0, 0.0, 1.0] ) - assert head[HeadPoseIndex.IS_VALID] is True + assert head[HeadInputIndex.IS_VALID] is True def test_head_source_compute_inactive(self): """Test that inactive head (TrackedT.data is None) produces absent output.""" @@ -447,7 +447,7 @@ def test_active_head_produces_data(self): assert not outputs["head"].is_none np.testing.assert_array_almost_equal( - outputs["head"][HeadPoseIndex.POSITION], [0.5, 1.5, 0.0] + outputs["head"][HeadInputIndex.POSITION], [0.5, 1.5, 0.0] ) def test_inactive_head_sets_none(self): @@ -473,4 +473,4 @@ def test_absent_head_raises_on_access(self): source.compute(inputs, outputs) with pytest.raises(ValueError, match="absent"): - _ = outputs["head"][HeadPoseIndex.POSITION] + _ = outputs["head"][HeadInputIndex.POSITION] diff --git a/src/core/retargeting_engine_tests/python/test_transforms.py b/src/core/retargeting_engine_tests/python/test_transforms.py index c1e065750..c5eb22048 100644 --- a/src/core/retargeting_engine_tests/python/test_transforms.py +++ b/src/core/retargeting_engine_tests/python/test_transforms.py @@ -17,8 +17,8 @@ from isaacteleop.retargeting_engine.interface import TensorGroup from isaacteleop.retargeting_engine.interface.tensor_group import OptionalTensorGroup from isaacteleop.retargeting_engine.tensor_types import ( - HeadPose, - HeadPoseIndex, + HeadInput, + HeadInputIndex, HandInput, ControllerInput, TransformMatrix, @@ -292,10 +292,10 @@ def test_preserves_shape_and_dtype(self): class TestHeadTransform: def _make_head_input(self, position, orientation, is_valid=True): - tg = TensorGroup(HeadPose()) - tg[HeadPoseIndex.POSITION] = np.array(position, dtype=np.float32) - tg[HeadPoseIndex.ORIENTATION] = np.array(orientation, dtype=np.float32) - tg[HeadPoseIndex.IS_VALID] = is_valid + tg = TensorGroup(HeadInput()) + tg[HeadInputIndex.POSITION] = np.array(position, dtype=np.float32) + tg[HeadInputIndex.ORIENTATION] = np.array(orientation, dtype=np.float32) + tg[HeadInputIndex.IS_VALID] = is_valid return tg def test_identity_transform(self): @@ -305,12 +305,12 @@ def test_identity_transform(self): result = _run_retargeter(node, {"head": head_in, "transform": xform_in}) out = result["head"] npt.assert_array_almost_equal( - np.from_dlpack(out[HeadPoseIndex.POSITION]), [1, 2, 3], decimal=5 + np.from_dlpack(out[HeadInputIndex.POSITION]), [1, 2, 3], decimal=5 ) npt.assert_array_almost_equal( - np.from_dlpack(out[HeadPoseIndex.ORIENTATION]), [0, 0, 0, 1], decimal=5 + np.from_dlpack(out[HeadInputIndex.ORIENTATION]), [0, 0, 0, 1], decimal=5 ) - assert out[HeadPoseIndex.IS_VALID] is True + assert out[HeadInputIndex.IS_VALID] is True def test_translation_transform(self): node = HeadTransform("head_xform") @@ -319,11 +319,11 @@ def test_translation_transform(self): result = _run_retargeter(node, {"head": head_in, "transform": xform_in}) out = result["head"] npt.assert_array_almost_equal( - np.from_dlpack(out[HeadPoseIndex.POSITION]), [11, 20, 30], decimal=5 + np.from_dlpack(out[HeadInputIndex.POSITION]), [11, 20, 30], decimal=5 ) # Orientation unchanged by pure translation npt.assert_array_almost_equal( - np.from_dlpack(out[HeadPoseIndex.ORIENTATION]), [0, 0, 0, 1], decimal=5 + np.from_dlpack(out[HeadInputIndex.ORIENTATION]), [0, 0, 0, 1], decimal=5 ) def test_rotation_transform(self): @@ -333,7 +333,7 @@ def test_rotation_transform(self): result = _run_retargeter(node, {"head": head_in, "transform": xform_in}) out = result["head"] npt.assert_array_almost_equal( - np.from_dlpack(out[HeadPoseIndex.POSITION]), [0, 1, 0], decimal=5 + np.from_dlpack(out[HeadInputIndex.POSITION]), [0, 1, 0], decimal=5 ) def test_passthrough_fields_preserved(self): @@ -342,7 +342,7 @@ def test_passthrough_fields_preserved(self): xform_in = _make_transform_input(_rotation_z_90_with_translation()) result = _run_retargeter(node, {"head": head_in, "transform": xform_in}) out = result["head"] - assert out[HeadPoseIndex.IS_VALID] is False + assert out[HeadInputIndex.IS_VALID] is False # ============================================================================ @@ -614,12 +614,12 @@ class TestHeadTransformNoAliasing: def test_mutating_output_position_does_not_affect_input(self): node = HeadTransform("head_xform") - head_in = TensorGroup(HeadPose()) - head_in[HeadPoseIndex.POSITION] = np.array([1.0, 2.0, 3.0], dtype=np.float32) - head_in[HeadPoseIndex.ORIENTATION] = np.array( + head_in = TensorGroup(HeadInput()) + head_in[HeadInputIndex.POSITION] = np.array([1.0, 2.0, 3.0], dtype=np.float32) + head_in[HeadInputIndex.ORIENTATION] = np.array( [0.0, 0.0, 0.0, 1.0], dtype=np.float32 ) - head_in[HeadPoseIndex.IS_VALID] = True + head_in[HeadInputIndex.IS_VALID] = True xform_in = _make_transform_input(_identity_4x4()) # Save a copy of the original input values @@ -630,19 +630,19 @@ def test_mutating_output_position_does_not_affect_input(self): out = result["head"] # Mutate the output position in-place - out_pos = np.from_dlpack(out[HeadPoseIndex.POSITION]) + out_pos = np.from_dlpack(out[HeadInputIndex.POSITION]) out_pos[:] = [99.0, 99.0, 99.0] # Mutate the output orientation in-place - out_ori = np.from_dlpack(out[HeadPoseIndex.ORIENTATION]) + out_ori = np.from_dlpack(out[HeadInputIndex.ORIENTATION]) out_ori[:] = [1.0, 1.0, 1.0, 0.0] # Original input must be unchanged npt.assert_array_equal( - np.from_dlpack(head_in[HeadPoseIndex.POSITION]), orig_pos + np.from_dlpack(head_in[HeadInputIndex.POSITION]), orig_pos ) npt.assert_array_equal( - np.from_dlpack(head_in[HeadPoseIndex.ORIENTATION]), orig_ori + np.from_dlpack(head_in[HeadInputIndex.ORIENTATION]), orig_ori ) @@ -913,10 +913,10 @@ class TestHeadTransformOptionalPropagation: """Verify HeadTransform propagates absent inputs to absent outputs.""" def _make_active_head(self): - tg = TensorGroup(HeadPose()) - tg[HeadPoseIndex.POSITION] = np.array([1, 2, 3], dtype=np.float32) - tg[HeadPoseIndex.ORIENTATION] = np.array([0, 0, 0, 1], dtype=np.float32) - tg[HeadPoseIndex.IS_VALID] = True + tg = TensorGroup(HeadInput()) + tg[HeadInputIndex.POSITION] = np.array([1, 2, 3], dtype=np.float32) + tg[HeadInputIndex.ORIENTATION] = np.array([0, 0, 0, 1], dtype=np.float32) + tg[HeadInputIndex.IS_VALID] = True return tg def test_output_spec_is_optional(self): @@ -940,7 +940,7 @@ def test_active_head(self): def test_absent_head_propagates(self): node = HeadTransform("xform") - head = OptionalTensorGroup(HeadPose()) + head = OptionalTensorGroup(HeadInput()) xform = _make_transform_input(_identity_4x4()) result = _run_retargeter(node, {"head": head, "transform": xform}) @@ -949,13 +949,13 @@ def test_absent_head_propagates(self): def test_absent_output_raises_on_read(self): node = HeadTransform("xform") - head = OptionalTensorGroup(HeadPose()) + head = OptionalTensorGroup(HeadInput()) xform = _make_transform_input(_identity_4x4()) result = _run_retargeter(node, {"head": head, "transform": xform}) with pytest.raises(ValueError, match="absent"): - _ = result["head"][HeadPoseIndex.POSITION] + _ = result["head"][HeadInputIndex.POSITION] class TestTransformOptionalNoneToggle: @@ -970,22 +970,22 @@ def test_head_transform_absent_present_cycle(self) -> None: node = HeadTransform("head_toggle") xform_90 = _make_transform_input(_rotation_z_90()) xform_id = _make_transform_input(_identity_4x4()) - absent = OptionalTensorGroup(HeadPose()) + absent = OptionalTensorGroup(HeadInput()) r1 = _run_retargeter(node, {"head": absent, "transform": xform_id}) assert r1["head"].is_none - active = TensorGroup(HeadPose()) - active[HeadPoseIndex.POSITION] = np.array([1.0, 0.0, 0.0], dtype=np.float32) - active[HeadPoseIndex.ORIENTATION] = np.array( + active = TensorGroup(HeadInput()) + active[HeadInputIndex.POSITION] = np.array([1.0, 0.0, 0.0], dtype=np.float32) + active[HeadInputIndex.ORIENTATION] = np.array( [0.0, 0.0, 0.0, 1.0], dtype=np.float32 ) - active[HeadPoseIndex.IS_VALID] = True + active[HeadInputIndex.IS_VALID] = True r2 = _run_retargeter(node, {"head": active, "transform": xform_90}) assert not r2["head"].is_none npt.assert_array_almost_equal( - np.from_dlpack(r2["head"][HeadPoseIndex.POSITION]), + np.from_dlpack(r2["head"][HeadInputIndex.POSITION]), [0.0, 1.0, 0.0], decimal=5, ) @@ -996,7 +996,7 @@ def test_head_transform_absent_present_cycle(self) -> None: r4 = _run_retargeter(node, {"head": active, "transform": xform_id}) assert not r4["head"].is_none npt.assert_array_almost_equal( - np.from_dlpack(r4["head"][HeadPoseIndex.POSITION]), + np.from_dlpack(r4["head"][HeadInputIndex.POSITION]), [1.0, 0.0, 0.0], decimal=5, ) diff --git a/src/core/retargeting_engine_tests/python/transform_numpy_version_smoke.py b/src/core/retargeting_engine_tests/python/transform_numpy_version_smoke.py index dd328a989..de5431218 100644 --- a/src/core/retargeting_engine_tests/python/transform_numpy_version_smoke.py +++ b/src/core/retargeting_engine_tests/python/transform_numpy_version_smoke.py @@ -36,18 +36,18 @@ def main() -> None: from isaacteleop.retargeting_engine.interface import TensorGroup from isaacteleop.retargeting_engine.tensor_types import ( - HeadPose, - HeadPoseIndex, + HeadInput, + HeadInputIndex, TransformMatrix, ) from isaacteleop.retargeting_engine.utilities import HeadTransform - head_in = TensorGroup(HeadPose()) - head_in[HeadPoseIndex.POSITION] = np.array([1.0, 2.0, 3.0], dtype=np.float32) - head_in[HeadPoseIndex.ORIENTATION] = np.array( + head_in = TensorGroup(HeadInput()) + head_in[HeadInputIndex.POSITION] = np.array([1.0, 2.0, 3.0], dtype=np.float32) + head_in[HeadInputIndex.ORIENTATION] = np.array( [0.0, 0.0, 0.0, 1.0], dtype=np.float32 ) - head_in[HeadPoseIndex.IS_VALID] = True + head_in[HeadInputIndex.IS_VALID] = True xform_in = TensorGroup(TransformMatrix()) xform_in[0] = np.eye(4, dtype=np.float32) @@ -55,7 +55,7 @@ def main() -> None: node = HeadTransform("numpy_smoke_head") result = node({"head": head_in, "transform": xform_in}) out = result["head"] - pos = out[HeadPoseIndex.POSITION] + pos = out[HeadInputIndex.POSITION] if not np.allclose(pos, [1.0, 2.0, 3.0], rtol=0, atol=1e-4): raise AssertionError(f"unexpected position {pos!r}") diff --git a/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/head_source.py b/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/head_source.py index f0e968fb8..0c56ecabc 100644 --- a/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/head_source.py +++ b/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/head_source.py @@ -1,10 +1,10 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """ Head Source Node - DeviceIO to Retargeting Engine converter. -Converts raw HeadPoseT flatbuffer data to standard HeadPose tensor format. +Converts raw HeadPoseT flatbuffer data to standard HeadInput tensor format. """ import numpy as np @@ -18,7 +18,7 @@ from ..interface.tensor_group import TensorGroup from ..interface.tensor_group_type import OptionalType from ..interface.retargeter_subgraph import RetargeterSubgraph -from ..tensor_types import HeadPose, HeadPoseIndex +from ..tensor_types import HeadInput, HeadInputIndex from .deviceio_tensor_types import DeviceIOHeadPoseTracked if TYPE_CHECKING: @@ -28,7 +28,7 @@ class HeadSource(IDeviceIOSource): """ - Stateless converter: DeviceIO HeadPoseT → HeadPose tensor. + Stateless converter: DeviceIO HeadPoseT → HeadInput tensor. Inputs: - "deviceio_head": Raw HeadPoseT flatbuffer object from DeviceIO @@ -87,11 +87,11 @@ def input_spec(self) -> RetargeterIOType: def output_spec(self) -> RetargeterIOType: """Declare standard head pose output (Optional — may be absent).""" - return {"head": OptionalType(HeadPose())} + return {"head": OptionalType(HeadInput())} def _compute_fn(self, inputs: RetargeterIO, outputs: RetargeterIO, context) -> None: """ - Convert DeviceIO HeadPoseTrackedT to standard HeadPose tensor. + Convert DeviceIO HeadPoseTrackedT to standard HeadInput tensor. Calls ``set_none()`` on the output when head tracking is inactive. @@ -127,9 +127,9 @@ def _compute_fn(self, inputs: RetargeterIO, outputs: RetargeterIO, context) -> N dtype=np.float32, ) - output[HeadPoseIndex.POSITION] = position - output[HeadPoseIndex.ORIENTATION] = orientation - output[HeadPoseIndex.IS_VALID] = head_pose.is_valid + output[HeadInputIndex.POSITION] = position + output[HeadInputIndex.ORIENTATION] = orientation + output[HeadInputIndex.IS_VALID] = head_pose.is_valid def transformed(self, transform_input: OutputSelector) -> RetargeterSubgraph: """ @@ -140,7 +140,7 @@ def transformed(self, transform_input: OutputSelector) -> RetargeterSubgraph: (e.g., value_input.output("value")). Returns: - A RetargeterSubgraph with output "head" containing the transformed HeadPose. + A RetargeterSubgraph with output "head" containing the transformed HeadInput. Example: head_source = HeadSource("head") diff --git a/src/python/isaacteleop/retargeting_engine/tensor_types/__init__.py b/src/python/isaacteleop/retargeting_engine/tensor_types/__init__.py index 22989ada2..5406ef949 100644 --- a/src/python/isaacteleop/retargeting_engine/tensor_types/__init__.py +++ b/src/python/isaacteleop/retargeting_engine/tensor_types/__init__.py @@ -9,7 +9,7 @@ from .ndarray_types import NDArrayType, DLDeviceType, DLDataType from .standard_types import ( HandInput, - HeadPose, + HeadInput, ControllerInput, FullBodyInput, TransformMatrix, @@ -30,7 +30,7 @@ ) from .indices import ( HandInputIndex, - HeadPoseIndex, + HeadInputIndex, ControllerInputIndex, Generic3AxisPedalInputIndex, FullBodyInputIndex, @@ -50,7 +50,7 @@ "DLDataType", # Standard types "HandInput", - "HeadPose", + "HeadInput", "ControllerInput", "FullBodyInput", "TransformMatrix", @@ -69,7 +69,7 @@ "NUM_END_EFFECTOR_FORCE_AXES", # Indices "HandInputIndex", - "HeadPoseIndex", + "HeadInputIndex", "ControllerInputIndex", "Generic3AxisPedalInputIndex", "FullBodyInputIndex", @@ -85,6 +85,10 @@ _DEPRECATED_ALIASES = { "BodyJointPicoIndex": "BodyJointIndex", "NUM_BODY_JOINTS_PICO": "NUM_BODY_JOINTS", + # Renamed for consistency with its siblings (HandInput, ControllerInput, + # FullBodyInput, Generic3AxisPedalInput); the group's fields are unchanged. + "HeadPose": "HeadInput", + "HeadPoseIndex": "HeadInputIndex", } diff --git a/src/python/isaacteleop/retargeting_engine/tensor_types/indices.py b/src/python/isaacteleop/retargeting_engine/tensor_types/indices.py index 513c33b49..d35b6461a 100644 --- a/src/python/isaacteleop/retargeting_engine/tensor_types/indices.py +++ b/src/python/isaacteleop/retargeting_engine/tensor_types/indices.py @@ -5,7 +5,7 @@ Dynamically generated indices for standard TensorGroupTypes. This module provides IntEnum classes for indexing into standard tensor groups -(HandInput, HeadPose, ControllerInput, Generic3AxisPedalInput, FullBodyInput) and standard joint arrays +(HandInput, HeadInput, ControllerInput, Generic3AxisPedalInput, FullBodyInput) and standard joint arrays (HandJointIndex, BodyJointIndex). The indices for TensorGroupTypes are generated automatically from the type definitions @@ -17,7 +17,7 @@ from enum import IntEnum from .standard_types import ( HandInput, - HeadPose, + HeadInput, ControllerInput, Generic3AxisPedalInput, FullBodyInput, @@ -37,7 +37,7 @@ def _create_index_enum(name: str, group_type, prefix: str = "") -> IntEnum: # Generate indices dynamically HandInputIndex: Any = _create_index_enum("HandInputIndex", HandInput(), "hand_") -HeadPoseIndex: Any = _create_index_enum("HeadPoseIndex", HeadPose(), "head_") +HeadInputIndex: Any = _create_index_enum("HeadInputIndex", HeadInput(), "head_") ControllerInputIndex: Any = _create_index_enum( "ControllerInputIndex", ControllerInput(), "controller_" ) diff --git a/src/python/isaacteleop/retargeting_engine/tensor_types/standard_types.py b/src/python/isaacteleop/retargeting_engine/tensor_types/standard_types.py index 382dcc82b..1b16072ef 100644 --- a/src/python/isaacteleop/retargeting_engine/tensor_types/standard_types.py +++ b/src/python/isaacteleop/retargeting_engine/tensor_types/standard_types.py @@ -88,7 +88,7 @@ def input_spec(self) -> RetargeterIO: # ============================================================================ -def HeadPose() -> TensorGroupType: +def HeadInput() -> TensorGroupType: """ Standard TensorGroupType for head tracking data. diff --git a/src/python/isaacteleop/retargeting_engine/utilities/head_transform.py b/src/python/isaacteleop/retargeting_engine/utilities/head_transform.py index 12ddb5553..1b15853c3 100644 --- a/src/python/isaacteleop/retargeting_engine/utilities/head_transform.py +++ b/src/python/isaacteleop/retargeting_engine/utilities/head_transform.py @@ -16,7 +16,7 @@ from ..interface.base_retargeter import BaseRetargeter from ..interface.retargeter_core_types import RetargeterIO, RetargeterIOType from ..interface.tensor_group_type import OptionalType -from ..tensor_types import HeadPose, HeadPoseIndex, TransformMatrix +from ..tensor_types import HeadInput, HeadInputIndex, TransformMatrix from .transform_utils import ( _copy_tensor_group_slots_from_dlpack_input, decompose_transform, @@ -40,11 +40,11 @@ class HeadTransform(BaseRetargeter): sourced from a TransformSource node in the graph. Inputs: - - "head": OptionalType(HeadPose) tensor (position, orientation, is_valid) + - "head": OptionalType(HeadInput) tensor (position, orientation, is_valid) - "transform": TransformMatrix tensor containing the (4, 4) matrix Outputs: - - "head": OptionalType(HeadPose) tensor with transformed position and orientation + - "head": OptionalType(HeadInput) tensor with transformed position and orientation Example: transform_input = ValueInput("xform_input", TransformMatrix()) @@ -68,13 +68,13 @@ def __init__(self, name: str) -> None: def input_spec(self) -> RetargeterIOType: """Declare head pose (Optional) and transform matrix inputs.""" return { - "head": OptionalType(HeadPose()), + "head": OptionalType(HeadInput()), "transform": TransformMatrix(), } def output_spec(self) -> RetargeterIOType: """Declare transformed head pose output (Optional).""" - return {"head": OptionalType(HeadPose())} + return {"head": OptionalType(HeadInput())} def _compute_fn(self, inputs: RetargeterIO, outputs: RetargeterIO, context) -> None: """ @@ -104,5 +104,5 @@ def _compute_fn(self, inputs: RetargeterIO, outputs: RetargeterIO, context) -> N _copy_tensor_group_slots_from_dlpack_input(inp, out) - transform_position(out[HeadPoseIndex.POSITION], rotation, translation) - transform_orientation(out[HeadPoseIndex.ORIENTATION], rotation) + transform_position(out[HeadInputIndex.POSITION], rotation, translation) + transform_orientation(out[HeadInputIndex.ORIENTATION], rotation) From 6d6c87a3eea669105f8db77556029b84a59ad401 Mon Sep 17 00:00:00 2001 From: Andrei Aristarkhov Date: Thu, 30 Jul 2026 17:16:13 -0700 Subject: [PATCH 2/2] fix(retargeting): complete the HeadPose -> HeadInput deprecation shims The rename added deprecated aliases only to the tensor_types package __init__, but examples and downstream code import through the submodule paths (tensor_types.indices, tensor_types.standard_types), which had no aliases and raised ImportError. - indices.py: add HeadPoseIndex -> HeadInputIndex to _DEPRECATED_ALIASES - standard_types.py: add the _DEPRECATED_ALIASES/__getattr__ pair with HeadPose -> HeadInput, mirroring the existing pattern - deviceio_source_nodes/interface.py: fix stale docstring reference - test_tensor_group.py: cover both package- and submodule-level aliases Signed-off-by: Andrei Aristarkhov --- .../python/test_tensor_group.py | 26 ++++++++++++++++++- .../deviceio_source_nodes/interface.py | 4 +-- .../tensor_types/indices.py | 11 +++++--- .../tensor_types/standard_types.py | 23 ++++++++++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/core/retargeting_engine_tests/python/test_tensor_group.py b/src/core/retargeting_engine_tests/python/test_tensor_group.py index a1646e062..f64637e92 100644 --- a/src/core/retargeting_engine_tests/python/test_tensor_group.py +++ b/src/core/retargeting_engine_tests/python/test_tensor_group.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """ @@ -642,5 +642,29 @@ def test_copy_returns_optional_tensor_group_type(self): assert type(cp) is OptionalTensorGroup +class TestDeprecatedHeadAliases: + """``HeadPose``/``HeadPoseIndex`` still resolve to the renamed ``HeadInput`` + names and emit a DeprecationWarning on access.""" + + def test_package_level_aliases_resolve_and_warn(self): + from isaacteleop.retargeting_engine import tensor_types + + for old, new in ( + ("HeadPose", "HeadInput"), + ("HeadPoseIndex", "HeadInputIndex"), + ): + with pytest.warns(DeprecationWarning, match=old): + deprecated = getattr(tensor_types, old) + assert deprecated is getattr(tensor_types, new) + + def test_submodule_aliases_resolve_and_warn(self): + from isaacteleop.retargeting_engine.tensor_types import indices, standard_types + + with pytest.warns(DeprecationWarning, match="HeadPose"): + assert standard_types.HeadPose is standard_types.HeadInput + with pytest.warns(DeprecationWarning, match="HeadPoseIndex"): + assert indices.HeadPoseIndex is indices.HeadInputIndex + + if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/interface.py b/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/interface.py index 5608409d3..35849b147 100644 --- a/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/interface.py +++ b/src/python/isaacteleop/retargeting_engine/deviceio_source_nodes/interface.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """ @@ -26,7 +26,7 @@ class IDeviceIOSource(BaseRetargeter): DeviceIO source nodes are retargeters that: - Take DeviceIO tracked wrappers as input (DeviceIOHeadPoseTracked, DeviceIOHandPoseTracked, etc.) - - Convert them to standard retargeting engine tensor formats (HeadPose, HandInput, etc.) + - Convert them to standard retargeting engine tensor formats (HeadInput, HandInput, etc.) - Are pure converters with no internal state or session dependencies - Provide access to their associated tracker via get_tracker() - Know how to poll their own tracker via poll_tracker() diff --git a/src/python/isaacteleop/retargeting_engine/tensor_types/indices.py b/src/python/isaacteleop/retargeting_engine/tensor_types/indices.py index d35b6461a..44c4e178f 100644 --- a/src/python/isaacteleop/retargeting_engine/tensor_types/indices.py +++ b/src/python/isaacteleop/retargeting_engine/tensor_types/indices.py @@ -109,9 +109,14 @@ class BodyJointIndex(IntEnum): RIGHT_HAND = 23 -# Deprecated alias for BodyJointIndex, resolved lazily via __getattr__ so accessing -# it emits a DeprecationWarning. -_DEPRECATED_ALIASES = {"BodyJointPicoIndex": "BodyJointIndex"} +# Deprecated aliases resolved lazily via __getattr__ so accessing them emits a +# DeprecationWarning. +_DEPRECATED_ALIASES = { + "BodyJointPicoIndex": "BodyJointIndex", + # Renamed for consistency with its siblings (HandInputIndex, ControllerInputIndex, + # FullBodyInputIndex); the underlying fields are unchanged. + "HeadPoseIndex": "HeadInputIndex", +} def __getattr__(name: str): diff --git a/src/python/isaacteleop/retargeting_engine/tensor_types/standard_types.py b/src/python/isaacteleop/retargeting_engine/tensor_types/standard_types.py index 1b16072ef..9b15c9858 100644 --- a/src/python/isaacteleop/retargeting_engine/tensor_types/standard_types.py +++ b/src/python/isaacteleop/retargeting_engine/tensor_types/standard_types.py @@ -8,6 +8,8 @@ and provide type-safe specifications for hand tracking, head tracking, and controller data. """ +import warnings + from ..interface.tensor_group_type import TensorGroupType from .scalar_types import FloatType, BoolType from .ndarray_types import NDArrayType, DLDataType @@ -326,3 +328,24 @@ def Generic3AxisPedalInput() -> TensorGroupType: FloatType("pedal_rudder"), ], ) + + +# Deprecated aliases resolved lazily via __getattr__ so accessing them emits a +# DeprecationWarning. +_DEPRECATED_ALIASES = { + # Renamed for consistency with its siblings (HandInput, ControllerInput, + # FullBodyInput, Generic3AxisPedalInput); the group's fields are unchanged. + "HeadPose": "HeadInput", +} + + +def __getattr__(name: str): + new_name = _DEPRECATED_ALIASES.get(name) + if new_name is not None: + warnings.warn( + f"{name} is deprecated; use {new_name} instead.", + DeprecationWarning, + stacklevel=2, + ) + return globals()[new_name] + raise AttributeError(f"module {__name__!r} has no attribute {name!r}")