Skip to content

Commit b041aa5

Browse files
committed
fix(retargeting): DLPack copy order and optional transform tests
- Copy ndarray slots via np.from_dlpack(input).copy() before in-place transforms - Numpy version tests / smoke; requirements numpy>=1.23 - Tests for optional absent/present toggling across calls
1 parent 94f657d commit b041aa5

12 files changed

Lines changed: 377 additions & 50 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ SPDX-License-Identifier: Apache-2.0
1111

1212
[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
1313
[![Isaac Lab](https://img.shields.io/badge/Isaac%20Lab-3.0.0-orange.svg)](https://isaac-sim.github.io/IsaacLab/develop)
14-
[![numpy](https://img.shields.io/badge/numpy-1.22%2B-lightgrey.svg)](https://numpy.org/)
14+
[![numpy](https://img.shields.io/badge/numpy-1.23%2B-lightgrey.svg)](https://numpy.org/)
1515
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
1616

1717

examples/oxr/python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ description = "OpenXR Tracking Python Examples"
88
requires-python = ">=3.10,<3.13"
99
dependencies = [
1010
"isaacteleop",
11-
"numpy>=1.22.2",
11+
"numpy>=1.23.0",
1212
]

examples/retargeting/python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ description = "Retargeting Engine Python Examples"
88
requires-python = ">=3.10,<3.13"
99
dependencies = [
1010
"isaacteleop[ui,retargeters-lite]",
11-
"numpy>=1.22.2",
11+
"numpy>=1.23.0",
1212
]

examples/teleop_session_manager/python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ description = "TeleopUtils Python Examples"
88
requires-python = ">=3.10,<3.13"
99
dependencies = [
1010
"isaacteleop[ui]",
11-
"numpy>=1.22.2",
11+
"numpy>=1.23.0",
1212
]

src/core/python/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
numpy>=1.22.2
1+
numpy>=1.23.0

src/core/retargeting_engine/python/utilities/controller_transform.py

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

44
"""
@@ -11,8 +11,6 @@
1111
provided by a TransformSource node.
1212
"""
1313

14-
import copy
15-
1614
import numpy as np
1715

1816
from ..interface.base_retargeter import BaseRetargeter
@@ -21,9 +19,10 @@
2119
from ..interface.tensor_group_type import OptionalType
2220
from ..tensor_types import ControllerInput, ControllerInputIndex, TransformMatrix
2321
from .transform_utils import (
22+
_copy_tensor_group_slots_from_dlpack_input,
2423
decompose_transform,
25-
transform_position,
2624
transform_orientation,
25+
transform_position,
2726
)
2827

2928

@@ -120,24 +119,13 @@ def _transform_controller(
120119
translation: np.ndarray,
121120
) -> None:
122121
"""Apply the transform to a single controller's data."""
123-
# Deep-copy all fields from input to output (avoid aliasing)
124-
for i in range(len(inp)):
125-
out[i] = copy.deepcopy(inp[i])
122+
_copy_tensor_group_slots_from_dlpack_input(inp, out)
126123

127-
# Transform pose fields in-place on the output buffers
128124
transform_position(
129-
np.from_dlpack(out[ControllerInputIndex.GRIP_POSITION]),
130-
rotation,
131-
translation,
132-
)
133-
transform_orientation(
134-
np.from_dlpack(out[ControllerInputIndex.GRIP_ORIENTATION]), rotation
125+
out[ControllerInputIndex.GRIP_POSITION], rotation, translation
135126
)
127+
transform_orientation(out[ControllerInputIndex.GRIP_ORIENTATION], rotation)
136128
transform_position(
137-
np.from_dlpack(out[ControllerInputIndex.AIM_POSITION]),
138-
rotation,
139-
translation,
140-
)
141-
transform_orientation(
142-
np.from_dlpack(out[ControllerInputIndex.AIM_ORIENTATION]), rotation
129+
out[ControllerInputIndex.AIM_POSITION], rotation, translation
143130
)
131+
transform_orientation(out[ControllerInputIndex.AIM_ORIENTATION], rotation)

src/core/retargeting_engine/python/utilities/hand_transform.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

44
"""
@@ -15,8 +15,6 @@
1515
the wrist while leaving other joints untransformed would break the hand skeleton.
1616
"""
1717

18-
import copy
19-
2018
import numpy as np
2119

2220
from ..interface.base_retargeter import BaseRetargeter
@@ -25,9 +23,10 @@
2523
from ..interface.tensor_group_type import OptionalType
2624
from ..tensor_types import HandInput, HandInputIndex, TransformMatrix
2725
from .transform_utils import (
26+
_copy_tensor_group_slots_from_dlpack_input,
2827
decompose_transform,
29-
transform_positions_batch,
3028
transform_orientations_batch,
29+
transform_positions_batch,
3130
)
3231

3332

@@ -126,14 +125,9 @@ def _transform_hand(
126125
translation: np.ndarray,
127126
) -> None:
128127
"""Apply the transform to a single hand's joint data."""
129-
# Deep-copy all fields from input to output (avoid aliasing)
130-
for i in range(len(inp)):
131-
out[i] = copy.deepcopy(inp[i])
128+
_copy_tensor_group_slots_from_dlpack_input(inp, out)
132129

133-
# Transform pose fields in-place on the output buffers
134130
transform_positions_batch(
135-
np.from_dlpack(out[HandInputIndex.JOINT_POSITIONS]), rotation, translation
136-
)
137-
transform_orientations_batch(
138-
np.from_dlpack(out[HandInputIndex.JOINT_ORIENTATIONS]), rotation
131+
out[HandInputIndex.JOINT_POSITIONS], rotation, translation
139132
)
133+
transform_orientations_batch(out[HandInputIndex.JOINT_ORIENTATIONS], rotation)

src/core/retargeting_engine/python/utilities/head_transform.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

44
"""
@@ -11,18 +11,17 @@
1111
provided by a TransformSource node.
1212
"""
1313

14-
import copy
15-
1614
import numpy as np
1715

1816
from ..interface.base_retargeter import BaseRetargeter
1917
from ..interface.retargeter_core_types import RetargeterIO, RetargeterIOType
2018
from ..interface.tensor_group_type import OptionalType
2119
from ..tensor_types import HeadPose, HeadPoseIndex, TransformMatrix
2220
from .transform_utils import (
21+
_copy_tensor_group_slots_from_dlpack_input,
2322
decompose_transform,
24-
transform_position,
2523
transform_orientation,
24+
transform_position,
2625
)
2726

2827

@@ -103,12 +102,7 @@ def _compute_fn(self, inputs: RetargeterIO, outputs: RetargeterIO, context) -> N
103102
inp = inputs["head"]
104103
out = outputs["head"]
105104

106-
# Deep-copy all fields from input to output (avoid aliasing)
107-
for i in range(len(inp)):
108-
out[i] = copy.deepcopy(inp[i])
105+
_copy_tensor_group_slots_from_dlpack_input(inp, out)
109106

110-
# Transform pose fields in-place on the output buffers
111-
transform_position(
112-
np.from_dlpack(out[HeadPoseIndex.POSITION]), rotation, translation
113-
)
114-
transform_orientation(np.from_dlpack(out[HeadPoseIndex.ORIENTATION]), rotation)
107+
transform_position(out[HeadPoseIndex.POSITION], rotation, translation)
108+
transform_orientation(out[HeadPoseIndex.ORIENTATION], rotation)

src/core/retargeting_engine/python/utilities/transform_utils.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@
1313
hands_source.py, controllers_source.py).
1414
"""
1515

16+
import copy
17+
from typing import Tuple, Union
18+
1619
import numpy as np
17-
from typing import Tuple
20+
21+
from ..interface.tensor_group import OptionalTensorGroup, TensorGroup
22+
from ..tensor_types import NDArrayType
1823

1924

2025
def validate_transform_matrix(matrix: np.ndarray) -> np.ndarray:
@@ -44,6 +49,19 @@ def validate_transform_matrix(matrix: np.ndarray) -> np.ndarray:
4449
return matrix
4550

4651

52+
def _copy_tensor_group_slots_from_dlpack_input(
53+
inp: Union[OptionalTensorGroup, TensorGroup],
54+
out: Union[OptionalTensorGroup, TensorGroup],
55+
) -> None:
56+
"""Copy slots *inp* → *out*; ndarray slots via ``from_dlpack`` then ``.copy()``."""
57+
58+
for i, tensor_type in enumerate(inp.group_type.types):
59+
if isinstance(tensor_type, NDArrayType):
60+
out[i] = np.from_dlpack(inp[i]).copy()
61+
else:
62+
out[i] = copy.deepcopy(inp[i])
63+
64+
4765
def decompose_transform(matrix: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
4866
"""
4967
Decompose a 4x4 homogeneous transform into rotation and translation.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""
5+
Run pose-transform smoke under isolated NumPy versions (1.23.x and 2.x).
6+
7+
NumPy 1.23 adds the public ``numpy.from_dlpack`` API used by the transform path.
8+
CTest sets ``PYTHONPATH`` to the built ``python_package`` tree; each test spawns
9+
``uv run --no-project`` with a pinned NumPy.
10+
11+
Python version comes from the **same interpreter as pytest** (CI matrix /
12+
``ISAAC_TELEOP_PYTHON_VERSION``), not a hard-coded list. That keeps ``uv run``
13+
aligned with the wheel ABI under ``PYTHONPATH`` (native extensions match).
14+
15+
The NumPy **1.23.5** pin is **skipped** on Python 3.12+ (no viable wheels / install
16+
for that interpreter). NumPy **2.x** runs on every matrix Python.
17+
"""
18+
19+
from __future__ import annotations
20+
21+
import os
22+
import shutil
23+
import subprocess
24+
import sys
25+
from pathlib import Path
26+
27+
import pytest
28+
29+
_SMOKE = Path(__file__).resolve().parent / "transform_numpy_version_smoke.py"
30+
31+
32+
@pytest.mark.parametrize(
33+
("numpy_pin", "version_key"),
34+
[
35+
("numpy==1.23.5", "1.23"),
36+
("numpy>=2.0.0,<3", "2"),
37+
],
38+
)
39+
def test_head_transform_smoke_isolated_numpy(numpy_pin: str, version_key: str) -> None:
40+
if shutil.which("uv") is None:
41+
pytest.skip("uv not on PATH")
42+
if "PYTHONPATH" not in os.environ:
43+
pytest.skip(
44+
"PYTHONPATH unset (run under CTest or point at build python_package/<CONFIG>)"
45+
)
46+
if not _SMOKE.is_file():
47+
pytest.fail(f"missing smoke script: {_SMOKE}")
48+
49+
if version_key == "1.23" and sys.version_info >= (3, 12):
50+
pytest.skip(
51+
"numpy==1.23.5 is not supported on Python 3.12+ (no wheels; matrix uses 3.12/3.13)"
52+
)
53+
54+
py = f"{sys.version_info.major}.{sys.version_info.minor}"
55+
cmd = [
56+
"uv",
57+
"run",
58+
"--no-project",
59+
"--python",
60+
py,
61+
"--with",
62+
numpy_pin,
63+
"python",
64+
str(_SMOKE),
65+
version_key,
66+
]
67+
subprocess.run(cmd, check=True, env=os.environ.copy())

0 commit comments

Comments
 (0)