Skip to content
Draft
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
2 changes: 1 addition & 1 deletion examples/camera_viz/pipeline/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def _record_error(self, exc: BaseException, where: str) -> None:
with self._error_lock:
if self._error is None:
self._error = exc
logger.error("VizRunner %s thread failed: %s", where, exc, exc_info=True)
logger.error("VizRunner %s thread failed: %s", where, exc, exc_info=exc)
self._stop.set()
with self._data_cond:
self._data_cond.notify_all()
Expand Down
4 changes: 2 additions & 2 deletions examples/camera_viz/sources/oakd.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import threading
import time
from dataclasses import dataclass, field
from typing import List, Optional
from typing import ClassVar, List, Optional

import numpy as np

Expand Down Expand Up @@ -120,7 +120,7 @@ class _OakdDevice:
closes it.
"""

_SOCKET_MAP = {
_SOCKET_MAP: ClassVar[dict[str, str]] = {
"RGB": "CAM_A",
"CAM_A": "CAM_A",
"LEFT": "CAM_B",
Expand Down
36 changes: 19 additions & 17 deletions examples/camera_viz/sources/synthetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,27 +234,29 @@ def _produce_loop(self) -> None:
diag_l = (x_grid_l + y_grid) / float(w + h)
diag_r = (x_grid_r + y_grid) / float(w + h)

# Defined once, above the loop: taking phase as a parameter avoids
# rebuilding a closure over it on every generated frame.
def fill(buf, diag, phase):
r = (cp.sin((diag + phase) * 6.2831853) * 127.0 + 128.0).astype(
cp.uint8
)
g = (
cp.sin((diag + phase + 0.3333) * 6.2831853) * 127.0 + 128.0
).astype(cp.uint8)
b = (
cp.sin((diag + phase + 0.6667) * 6.2831853) * 127.0 + 128.0
).astype(cp.uint8)
buf[..., 0] = r
buf[..., 1] = g
buf[..., 2] = b
buf[..., 3] = 255

while not self._stop.is_set():
t = (time.monotonic_ns() - self._t0_ns) * 1e-9
phase = (t * self._hue_speed_hz) % 1.0

def fill(buf, diag):
r = (cp.sin((diag + phase) * 6.2831853) * 127.0 + 128.0).astype(
cp.uint8
)
g = (
cp.sin((diag + phase + 0.3333) * 6.2831853) * 127.0 + 128.0
).astype(cp.uint8)
b = (
cp.sin((diag + phase + 0.6667) * 6.2831853) * 127.0 + 128.0
).astype(cp.uint8)
buf[..., 0] = r
buf[..., 1] = g
buf[..., 2] = b
buf[..., 3] = 255

fill(self._left[self._write_idx], diag_l)
fill(self._right[self._write_idx], diag_r)
fill(self._left[self._write_idx], diag_l, phase)
fill(self._right[self._write_idx], diag_r, phase)
cp.cuda.Stream.null.synchronize()

with self._lock:
Expand Down
2 changes: 1 addition & 1 deletion examples/camera_viz/sources/zed.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ def _hint():

# Pyzed Mats are pitched GPU buffers — one per eye, reused every
# grab() / retrieve_image() pair.
for eye, slot in self._slots.items():
for slot in self._slots.values():
slot.zed_mat = sl.Mat()

self._camera = camera
Expand Down
2 changes: 1 addition & 1 deletion examples/camera_viz/transports/rtp_h264_sender.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def _send_loop(self) -> None:
raise RuntimeError(
f"RtpH264Sender: encode failed {consecutive_encode_failures} "
f"times in a row; surfacing to supervisor for full restart"
)
) from e
continue

for pkt in packets:
Expand Down
4 changes: 2 additions & 2 deletions examples/noitom/noitom_retargeting.py
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,7 @@ def compute_robot_reference_positions(
parsed = _parse_upper_body(frame)
if parsed is None:
return {}
torso, left, right, _pelvis_world = parsed
_torso, left, right, _pelvis_world = parsed
yaw_delta = _resolve_yaw_delta(current_yaw - calib.body_yaw_isaac, settings)
anchor = settings.robot_pelvis_world.astype(np.float64)
positions: dict[int, np.ndarray] = {int(BodyJoint.PELVIS): anchor.copy()}
Expand Down Expand Up @@ -1754,7 +1754,7 @@ def _solve_wrist_target(
yaw_delta = _resolve_yaw_delta(_compute_torso_yaw(torso) - calib_yaw, settings)

if settings.use_posture_based_arms:
shoulder_robot, elbow_robot, wrist_robot = _arm_fk_robot_blended(
_shoulder_robot, elbow_robot, wrist_robot = _arm_fk_robot_blended(
arm, neutral, settings, yaw_delta, is_left
)
forearm = wrist_robot - elbow_robot
Expand Down
3 changes: 2 additions & 1 deletion examples/retargeting/python/wuji_hand_retargeter_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import signal
import sys
import time
from typing import ClassVar

import numpy as np

Expand Down Expand Up @@ -147,7 +148,7 @@ class _ReplayUnpickler(pickle.Unpickler):
recording may have been produced elsewhere or shared.
"""

_ALLOWED = {
_ALLOWED: ClassVar[set[tuple[str, str]]] = {
("numpy", "ndarray"),
("numpy", "dtype"),
("numpy.core.multiarray", "_reconstruct"), # numpy < 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ def _positive_int(value: str) -> int:
try:
n = int(value)
except ValueError:
raise argparse.ArgumentTypeError(f"expected a positive integer, got {value!r}")
raise argparse.ArgumentTypeError(
f"expected a positive integer, got {value!r}"
) from None
if n <= 0:
raise argparse.ArgumentTypeError(f"must be a positive integer, got {n}")
return n
Expand All @@ -44,7 +46,7 @@ def _parse_uuid_bytes(uuid_text: str) -> bytes:
raise argparse.ArgumentTypeError(
f"--channel-uuid: invalid UUID {uuid_text!r} (expected canonical form, "
"e.g. 550e8400-e29b-41d4-a716-446655440000)"
)
) from None


def _enqueue_outbound_message(sink, payload: bytes) -> None:
Expand Down
69 changes: 69 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,72 @@ packages = { isaacteleop = "src/python/isaacteleop" }
[tool.scikit-build.editable]
mode = "redirect"
rebuild = false

# ==============================================================================
# Ruff
# ==============================================================================
# The ruff + ruff-format pre-commit hooks previously ran with no configuration at
# all, i.e. only ruff's built-in default rules (E4/E7/E9 + F). The `select` below
# widens that to the correctness-oriented rule groups; formatting stays on
# ruff-format's defaults (88 cols), which is what the tree is already formatted to.
#
# Everything in `select` is currently clean, so the hook is enforceable as-is.
# Each `ignore` is a deliberate call, not a backlog marker -- see its comment.
[tool.ruff]
target-version = "py311"

[tool.ruff.lint]
select = [
"E4", "E7", "E9", # pycodestyle errors (ruff's default subset)
"F", # pyflakes
"B", # flake8-bugbear -- real bug shapes
"C4", # flake8-comprehensions
"PIE", # flake8-pie
"PERF", # perflint
"PLE", "PLW", # pylint errors + warnings
"LOG", "G", # logging correctness
"ASYNC", # async pitfalls
"RUF", # ruff-specific
]

ignore = [
# Adding strict= to a zip() converts a silent truncation into a runtime
# exception, so it is a behaviour change per call site, not a mechanical fix.
# Worth adopting deliberately (15 sites) rather than in a lint sweep.
"B905",
# Style-only rewrites of code that is already correct and readable:
# RUF005 turns `a + [b]` into `[*a, b]`, C408 turns dict(a=1) into {"a": 1},
# and RUF007 turns zip(x[:-1], x[1:]) into itertools.pairwise(x). Enforcing
# any of them means churning working call sites for no behaviour change, so
# they are left to author preference.
"RUF005", "C408", "RUF007",
# Sorting __all__ alphabetically destroys the semantic grouping comments the
# package __init__ files use ("# Hand types.", "# Controller types.", ...).
"RUF022",
# RUF100 is evaluated against `select` above, so it flags every noqa written
# for a rule this config does not (yet) enable -- BLE001, PLC0415, N803 and
# friends. Re-enable once those groups are adopted.
"RUF100",
# The tree deliberately uses en/em dashes and arrows in prose and comments.
"RUF001", "RUF002", "RUF003",
# Module-level singletons (EnvConfig, the CloudXR runtime handles) are an
# intentional design choice here.
"PLW0603",
# Rebinding a loop variable to its normalized form, and small wrapper lambdas,
# both read better than the suggested rewrites in the places they occur.
"PLW2901", "PLW0108",
# The suggested comprehensions inline filtering logic that is clearer as an
# explicit loop (see EnvConfig._merge_env).
"PERF401", "PERF403",
# Targets trio/anyio. The one flagged call is a sub-millisecond os.path.isfile
# in an asyncio poll loop that already sleeps between iterations; routing it
# through an executor would cost more than the stat it replaces.
"ASYNC240",
# Flags any async def taking a `timeout` parameter; that is the established
# signature for the adb/runtime helpers and their callers.
"ASYNC109",
]

[tool.ruff.lint.per-file-ignores]
# Tests legitimately assert on private state and re-import inside test bodies.
"**/tests/**" = ["SLF001"]
2 changes: 2 additions & 0 deletions scripts/check_copyright_year.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def git_last_modified_year(path: Path) -> int | None:
text=True,
cwd=path.parent,
timeout=5,
check=False,
)
if result.returncode != 0:
return None
Expand All @@ -41,6 +42,7 @@ def git_last_modified_year(path: Path) -> int | None:
text=True,
cwd=root,
timeout=5,
check=False,
)
if result.returncode != 0 or not result.stdout.strip():
return None
Expand Down
1 change: 1 addition & 0 deletions scripts/check_lfs_pointers.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def _staged_content(path: str) -> bytes | None:
proc = subprocess.run(
["git", "show", f":{path}"],
capture_output=True,
check=False,
)
if proc.returncode != 0:
return None
Expand Down
6 changes: 3 additions & 3 deletions src/core/cloudxr_tests/python/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def test_explicit_exp_missing_raises(self, monkeypatch):
"isaacteleop.cloudxr.runtime._is_exp_available", lambda: False
)
with pytest.raises(
RuntimeError, match="cloudxr_exp|ENABLE_CLOUDXR_EXP"
RuntimeError, match=r"cloudxr_exp|ENABLE_CLOUDXR_EXP"
) as excinfo:
resolve_cloudxr_runtime_module()
# The message must blame the missing *artifact*: cloudxr_exp is authored
Expand Down Expand Up @@ -240,7 +240,7 @@ def test_auto_t234_missing_raises(self, monkeypatch):
monkeypatch.setattr(
"isaacteleop.cloudxr.runtime._is_exp_available", lambda: False
)
with pytest.raises(RuntimeError, match="cloudxr_exp|ENABLE_CLOUDXR_EXP"):
with pytest.raises(RuntimeError, match=r"cloudxr_exp|ENABLE_CLOUDXR_EXP"):
resolve_cloudxr_runtime_module()


Expand Down Expand Up @@ -270,7 +270,7 @@ def test_missing_raises(self, tmp_path, monkeypatch):
self._select(monkeypatch, "isaacteleop.cloudxr")
_patch_find_spec(monkeypatch, {"isaacteleop.cloudxr": roots})

with pytest.raises(RuntimeError, match="libcloudxr.so") as excinfo:
with pytest.raises(RuntimeError, match=r"libcloudxr\.so") as excinfo:
get_sdk_path()
for root in roots:
assert root in str(excinfo.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ def test_output_selector_repr(self):
class ParametricScaleRetargeter(BaseRetargeter):
"""Retargeter with a tunable scale parameter."""

def __init__(self, name: str, config_file: str = None) -> None:
def __init__(self, name: str, config_file: str | None = None) -> None:
# Create parameter with sync function
parameters = [
FloatParameter(
Expand Down
7 changes: 4 additions & 3 deletions src/core/retargeting_engine_tests/python/test_haptic_sink.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,10 @@ def test_connect_rejects_mismatched_upstream_type(self) -> None:
sink = HapticSink("sink", device)

leaf = ValueInput("upstream", TactileVector(5))
with pytest.raises(Exception):
# Compatibility check raises a TypeError or AssertionError depending
# on which TensorType detects the mismatch first; either is fine.
# Which exception surfaces depends on which TensorType detects the mismatch
# first; NDArrayType currently raises ValueError. Naming the accepted set
# beats a bare Exception, which would also swallow an unrelated failure.
with pytest.raises((TypeError, AssertionError, ValueError)):
sink.connect({"left": leaf.output("value")})


Expand Down
13 changes: 6 additions & 7 deletions src/core/retargeting_engine_tests/python/test_parameter_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,12 @@ def test_save_and_load_to_file(self):
def test_load_nonexistent_file_doesnt_error(self):
"""Test loading from nonexistent file doesn't error."""
param = FloatParameter(name="value", description="Value", default_value=1.0)
try:
ParameterState(
name="test", parameters=[param], config_file="/nonexistent/path.json"
)
except Exception as e:
# Should not raise error during construction
assert False, f"Unexpected exception raised: {e}"
# Must not raise during construction; letting any exception propagate
# fails the test with the original traceback (and unlike `assert False`,
# this still holds under `python -O`).
ParameterState(
name="test", parameters=[param], config_file="/nonexistent/path.json"
)

def test_load_from_file_after_save(self):
"""Test loading from saved file."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,6 @@ def poll_tracker(self, deviceio_session):
class MockOpenXRHandles:
"""Mock OpenXR session handles."""

pass


class MockOpenXRSession:
"""Mock OpenXR session that supports context manager protocol."""
Expand Down Expand Up @@ -985,7 +983,7 @@ def test_external_leaves_missing_all_inputs(self):
config = make_config(pipeline)
session = TeleopSession(config)

with pytest.raises(ValueError, match="external.*non-DeviceIO"):
with pytest.raises(ValueError, match=r"external.*non-DeviceIO"):
session._validate_external_inputs(None)

def test_external_leaves_missing_some_inputs(self):
Expand Down Expand Up @@ -1573,7 +1571,7 @@ def test_step_raises_on_missing_external_inputs(self):
with mock_session_dependencies():
session = TeleopSession(config)
with session:
with pytest.raises(ValueError, match="external.*non-DeviceIO"):
with pytest.raises(ValueError, match=r"external.*non-DeviceIO"):
session.step()

def test_step_checks_plugin_health_every_60_frames(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ def _load_track_info_detection_lost(
for b, s in enumerate(stats):
# Find vis_mask for this track: track_info has track_id -> {index, vis_mask}; index matches our b
vis_mask = None
for tid, info in tracks.items():
for info in tracks.values():
if info.get("index") == b:
vis_mask = info.get("vis_mask")
break
Expand Down
3 changes: 2 additions & 1 deletion src/python/isaacteleop/cloudxr/env_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import shlex
import warnings
from pathlib import Path
from typing import ClassVar


class EnvConfig:
Expand Down Expand Up @@ -43,7 +44,7 @@ class EnvConfig:
)

# Default env var name -> default value. Empty string or None means "resolve later"
_DEFAULT_ENV: dict[str, str | None] = {
_DEFAULT_ENV: ClassVar[dict[str, str | None]] = {
"XR_RUNTIME_JSON": None, # resolved from openxr_run_dir()
"NV_CXR_RUNTIME_DIR": None, # resolved from openxr_run_dir()
"NV_CXR_OUTPUT_DIR": None, # resolved from ensure_logs_dir()
Expand Down
1 change: 1 addition & 0 deletions src/python/isaacteleop/cloudxr/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ def _cleanup_stale_runtime(env_cfg: EnvConfig) -> None:
["fuser", "-k", "-TERM", ipc_socket],
capture_output=True,
timeout=5,
check=False,
)
if result.returncode == 0:
time.sleep(1)
Expand Down
4 changes: 3 additions & 1 deletion src/python/isaacteleop/cloudxr/oob_teleop_adb.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,9 @@ def open_url_on_headset(url: str) -> tuple[int, str]:
redact_control_token(" ".join(shlex.quote(c) for c in full)),
)
try:
proc = subprocess.run(full, capture_output=True, text=True, timeout=30)
proc = subprocess.run(
full, capture_output=True, text=True, timeout=30, check=False
)
except subprocess.TimeoutExpired as e:
partial = (
(e.stderr or e.stdout or b"")
Expand Down
4 changes: 1 addition & 3 deletions src/python/isaacteleop/haptic_devices/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@ def __init__(
)
# Latest-wins per endpoint within a frame; emitted and cleared by flush.
self._pending: dict[Endpoint, _Pulse] = {}
self._error_logged: dict[Endpoint, bool] = {
endpoint: False for endpoint in self._endpoints
}
self._error_logged: dict[Endpoint, bool] = dict.fromkeys(self._endpoints, False)

def accepted_type(self) -> TensorGroupType:
return ControllerHapticPulse()
Expand Down
Loading