Unverified Commit e682605e authored by Mayank Mittal's avatar Mayank Mittal Committed by GitHub

Removes `compat` submodule from orbit (#455)

# Description

This MR removes `compat` submodule since we don't use/need it anymore.

## Type of change

- Breaking change (fix or feature that would cause existing
functionality to not work as expected)

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./orbit.sh --format`
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] I have run all the tests with `./orbit.sh --test` and they pass
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there
parent cb78f892
[package]
# Note: Semantic Versioning is used: https://semver.org/
version = "0.13.0"
version = "0.13.1"
# Description
title = "ORBIT framework for Robot Learning"
......
Changelog
---------
0.13.1 (2024-03-14)
~~~~~~~~~~~~~~~~~~~
Removed
^^^^^^^
* Removed the :mod:`omni.isaac.orbit.compat` module. This module was used to provide compatibility
with older versions of Isaac Sim. It is no longer needed since we have most of the functionality
absorbed into the main classes.
0.13.0 (2024-03-12)
~~~~~~~~~~~~~~~~~~~
......
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Sub-package containing compatibility code with previous release of Orbit.
.. note::
This package is not part of the public API and may be removed in future releases.
"""
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This submodule provides marker utilities for simplifying creation of UI elements in the GUI.
Currently, the module provides two classes:
* :class:`StaticMarker` for creating a group of markers from a single USD file.
* :class:`PointMarker` for creating a group of spheres.
.. note::
For some simple usecases, it may be sufficient to use the debug drawing utilities from Isaac Sim.
The debug drawing API is available in the `omni.isaac.debug_drawing`_ module. It allows drawing of
points and splines efficiently on the UI.
.. _omni.isaac.debug_drawing: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/ext_omni_isaac_debug_drawing.html
"""
from .point_marker import PointMarker
from .static_marker import StaticMarker
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""A class to coordinate groups of visual markers (loaded from USD)."""
from __future__ import annotations
import numpy as np
import torch
from collections.abc import Sequence
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from pxr import Gf, UsdGeom
class PointMarker:
"""A class to coordinate groups of visual sphere markers for goal-conditioned tasks.
This class allows visualization of multiple spheres. These can be used to represent a
goal-conditioned task. For instance, if a robot is performing a task of reaching a target, the
class can be used to display a red sphere when the target is far away and a green sphere when
the target is achieved. Otherwise, the class can be used to display spheres, for example, to
mark contact points.
The class uses `UsdGeom.PointInstancer`_ for efficient handling of multiple markers in the stage.
It creates two spherical markers of different colors. Based on the indices provided the referenced
marker is activated:
- :obj:`0` corresponds to unachieved target (red sphere).
- :obj:`1` corresponds to achieved target (green sphere).
Usage:
To create 24 point target markers of radius 0.2 and show them as achieved targets:
.. code-block:: python
from omni.isaac.orbit.compat.markers import PointMarker
# create a point marker
marker = PointMarker("/World/Visuals/goal", 24, radius=0.2)
# set position of the marker
marker_positions = np.random.uniform(-1.0, 1.0, (24, 3))
marker.set_world_poses(marker_positions)
# set status of the marker to show achieved targets
marker.set_status([1] * 24)
.. _UsdGeom.PointInstancer: https://graphics.pixar.com/usd/dev/api/class_usd_geom_point_instancer.html
"""
def __init__(self, prim_path: str, count: int, radius: float = 1.0):
"""Initialize the class.
Args:
prim_path: The prim path where the PointInstancer will be created.
count: The number of marker duplicates to create.
radius: The radius of the sphere. Defaults to 1.0.
Raises:
ValueError: When a prim already exists at the :obj:`prim_path` and it is not a :class:`UsdGeom.PointInstancer`.
"""
# check inputs
stage = stage_utils.get_current_stage()
# -- prim path
if prim_utils.is_prim_path_valid(prim_path):
prim = prim_utils.get_prim_at_path(prim_path)
if not prim.IsA(UsdGeom.PointInstancer):
raise ValueError(f"The prim at path {prim_path} cannot be parsed as a `PointInstancer` object")
self._instancer_manager = UsdGeom.PointInstancer(prim)
else:
self._instancer_manager = UsdGeom.PointInstancer.Define(stage, prim_path)
# store inputs
self.prim_path = prim_path
self.count = count
self._radius = radius
# create manager for handling instancing of frame markers
self._instancer_manager = UsdGeom.PointInstancer.Define(stage, prim_path)
# create a child prim for the marker
# -- target not achieved
prim = prim_utils.create_prim(f"{prim_path}/target_far", "Sphere", attributes={"radius": self._radius})
geom = UsdGeom.Sphere(prim)
geom.GetDisplayColorAttr().Set([(1.0, 0.0, 0.0)])
# -- target achieved
prim = prim_utils.create_prim(f"{prim_path}/target_close", "Sphere", attributes={"radius": self._radius})
geom = UsdGeom.Sphere(prim)
geom.GetDisplayColorAttr().Set([(0.0, 1.0, 0.0)])
# -- target invisible
prim = prim_utils.create_prim(f"{prim_path}/target_invisible", "Sphere", attributes={"radius": self._radius})
geom = UsdGeom.Sphere(prim)
geom.GetDisplayColorAttr().Set([(0.0, 0.0, 1.0)])
prim_utils.set_prim_visibility(prim, visible=False)
# add child reference to point instancer
relation_manager = self._instancer_manager.GetPrototypesRel()
relation_manager.AddTarget(f"{prim_path}/target_far") # target index: 0
relation_manager.AddTarget(f"{prim_path}/target_close") # target index: 1
relation_manager.AddTarget(f"{prim_path}/target_invisible") # target index: 2
# buffers for storing data in pixar Gf format
# FUTURE: Make them very far away from the scene?
self._proto_indices = [0] * self.count
self._gf_positions = [Gf.Vec3f() for _ in range(self.count)]
self._gf_orientations = [Gf.Quath() for _ in range(self.count)]
# FUTURE: add option to set scales
# specify that all initial prims are related to same geometry
self._instancer_manager.GetProtoIndicesAttr().Set(self._proto_indices)
# set initial positions of the targets
self._instancer_manager.GetPositionsAttr().Set(self._gf_positions)
self._instancer_manager.GetOrientationsAttr().Set(self._gf_orientations)
def set_visibility(self, visible: bool):
"""Sets the visibility of the markers.
The method does this through the USD API.
Args:
visible: flag to set the visibility.
"""
imageable = UsdGeom.Imageable(self._instancer_manager)
if visible:
imageable.MakeVisible()
else:
imageable.MakeInvisible()
def set_world_poses(
self,
positions: np.ndarray | torch.Tensor | None = None,
orientations: np.ndarray | torch.Tensor | None = None,
indices: Sequence[int] | None = None,
):
"""Update marker poses in the simulation world frame.
Args:
positions:
Positions in the world frame. Shape is (M, 3). Defaults to None, which means left unchanged.
orientations:
Quaternion orientations (w, x, y, z) in the world frame of the prims. Shape is (M, 4).
Defaults to None, which means left unchanged.
indices: Indices to specify which alter poses.
Shape is (M,), where M <= total number of markers. Defaults to None (i.e: all markers).
"""
# resolve inputs
if positions is not None:
positions = positions.tolist()
if orientations is not None:
orientations = orientations.tolist()
if indices is None:
indices = range(self.count)
# change marker locations
for i, marker_index in enumerate(indices):
if positions is not None:
self._gf_positions[marker_index][:] = positions[i]
if orientations is not None:
self._gf_orientations[marker_index].SetReal(orientations[i][0])
self._gf_orientations[marker_index].SetImaginary(orientations[i][1:])
# apply to instance manager
self._instancer_manager.GetPositionsAttr().Set(self._gf_positions)
self._instancer_manager.GetOrientationsAttr().Set(self._gf_orientations)
def set_status(self, status: list[int] | np.ndarray | torch.Tensor, indices: Sequence[int] | None = None):
"""Updates the marker activated by the instance manager.
Args:
status: Decides which prototype marker to visualize. Shape is (M)
indices: Indices to specify which alter poses.
Shape is (M,), where M <= total number of markers. Defaults to None (i.e: all markers).
"""
# default values
if indices is None:
indices = range(self.count)
# resolve input
if status is not list:
proto_indices = status.tolist()
else:
proto_indices = status
# change marker locations
for i, marker_index in enumerate(indices):
self._proto_indices[marker_index] = int(proto_indices[i])
# apply to instance manager
self._instancer_manager.GetProtoIndicesAttr().Set(self._proto_indices)
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""A class to coordinate groups of visual markers (loaded from USD)."""
from __future__ import annotations
import numpy as np
import torch
from collections.abc import Sequence
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from omni.isaac.core.materials import PreviewSurface
from omni.isaac.core.prims import GeometryPrim
from pxr import Gf, UsdGeom
import omni.isaac.orbit.compat.utils.kit as kit_utils
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR, check_file_path
class StaticMarker:
"""A class to coordinate groups of visual markers (loaded from USD).
This class allows visualization of different UI elements in the scene, such as points and frames.
The class uses `UsdGeom.PointInstancer`_ for efficient handling of the element in the stage
via instancing of the marker.
Usage:
To create 24 default frame markers with a scale of 0.5:
.. code-block:: python
from omni.isaac.orbit.compat.markers import StaticMarker
# create a static marker
marker = StaticMarker("/World/Visuals/frames", 24, scale=(0.5, 0.5, 0.5))
# set position of the marker
marker_positions = np.random.uniform(-1.0, 1.0, (24, 3))
marker.set_world_poses(marker_positions)
.. _UsdGeom.PointInstancer: https://graphics.pixar.com/usd/dev/api/class_usd_geom_point_instancer.html
"""
def __init__(
self,
prim_path: str,
count: int,
usd_path: str | None = None,
scale: tuple[float, float, float] = (1.0, 1.0, 1.0),
color: tuple[float, float, float] | None = None,
):
"""Initialize the class.
When the class is initialized, the :class:`UsdGeom.PointInstancer` is created into the stage
and the marker prim is registered into it.
Args:
prim_path: The prim path where the PointInstancer will be created.
count: The number of marker duplicates to create.
usd_path: The USD file path to the marker. Defaults to the USD path for the RGB frame axis marker.
scale: The scale of the marker. Defaults to (1.0, 1.0, 1.0).
color: The color of the marker. If provided, it overrides the existing color on all the
prims of the marker. Defaults to None.
Raises:
ValueError: When a prim already exists at the :obj:`prim_path` and it is not a
:class:`UsdGeom.PointInstancer`.
FileNotFoundError: When the USD file at :obj:`usd_path` does not exist.
"""
# resolve default markers in the UI elements
# -- USD path
if usd_path is None:
usd_path = f"{ISAAC_NUCLEUS_DIR}/Props/UIElements/frame_prim.usd"
else:
if not check_file_path(usd_path):
raise FileNotFoundError(f"USD file for the marker not found at: {usd_path}")
# -- prim path
stage = stage_utils.get_current_stage()
if prim_utils.is_prim_path_valid(prim_path):
# retrieve prim if it exists
prim = prim_utils.get_prim_at_path(prim_path)
if not prim.IsA(UsdGeom.PointInstancer):
raise ValueError(f"The prim at path {prim_path} cannot be parsed as a `PointInstancer` object")
self._instancer_manager = UsdGeom.PointInstancer(prim)
else:
# create a new prim
self._instancer_manager = UsdGeom.PointInstancer.Define(stage, prim_path)
# store inputs
self.prim_path = prim_path
self.count = count
self._usd_path = usd_path
# create manager for handling instancing of frame markers
self._instancer_manager = UsdGeom.PointInstancer.Define(stage, prim_path)
# create a child prim for the marker
prim_utils.create_prim(f"{prim_path}/marker", usd_path=self._usd_path)
# disable any physics on the marker
# FIXME: Also support disabling rigid body properties on the marker.
# Currently, it is not possible on GPU pipeline.
# kit_utils.set_nested_rigid_body_properties(f"{prim_path}/marker", rigid_body_enabled=False)
kit_utils.set_nested_collision_properties(f"{prim_path}/marker", collision_enabled=False)
# apply material to marker
if color is not None:
prim = GeometryPrim(f"{prim_path}/marker")
material = PreviewSurface(f"{prim_path}/markerColor", color=np.asarray(color))
prim.apply_visual_material(material, weaker_than_descendants=False)
# add child reference to point instancer
# FUTURE: Add support for multiple markers in the same instance manager?
relation_manager = self._instancer_manager.GetPrototypesRel()
relation_manager.AddTarget(f"{prim_path}/marker") # target index: 0
# buffers for storing data in pixar Gf format
# FUTURE: Make them very far away from the scene?
self._gf_positions = [Gf.Vec3f() for _ in range(self.count)]
self._gf_orientations = [Gf.Quath() for _ in range(self.count)]
self._gf_scales = [Gf.Vec3f(*tuple(scale)) for _ in range(self.count)]
# specify that all vis prims are related to same geometry
self._instancer_manager.GetProtoIndicesAttr().Set([0] * self.count)
# set initial positions of the targets
self._instancer_manager.GetScalesAttr().Set(self._gf_scales)
self._instancer_manager.GetPositionsAttr().Set(self._gf_positions)
self._instancer_manager.GetOrientationsAttr().Set(self._gf_orientations)
def set_visibility(self, visible: bool):
"""Sets the visibility of the markers.
The method does this through the USD API.
Args:
visible: flag to set the visibility.
"""
imageable = UsdGeom.Imageable(self._instancer_manager)
if visible:
imageable.MakeVisible()
else:
imageable.MakeInvisible()
def set_world_poses(
self,
positions: np.ndarray | torch.Tensor | None = None,
orientations: np.ndarray | torch.Tensor | None = None,
indices: Sequence[int] | None = None,
):
"""Update marker poses in the simulation world frame.
Args:
positions: Positions in the world frame. Shape is (M, 3). Defaults to None, which means left unchanged.
orientations: Quaternion orientations (w, x, y, z) in the world frame of the prims. Shape is (M, 4).
Defaults to None, which means left unchanged.
indices: Indices to specify which alter poses. Shape is (M,) where M <= total number of markers.
Defaults to None (i.e: all markers).
"""
# resolve inputs
if positions is not None:
positions = positions.tolist()
if orientations is not None:
orientations = orientations.tolist()
if indices is None:
indices = range(self.count)
# change marker locations
for i, marker_index in enumerate(indices):
if positions is not None:
self._gf_positions[marker_index][:] = positions[i]
if orientations is not None:
self._gf_orientations[marker_index].SetReal(orientations[i][0])
self._gf_orientations[marker_index].SetImaginary(orientations[i][1:])
# apply to instance manager
self._instancer_manager.GetPositionsAttr().Set(self._gf_positions)
self._instancer_manager.GetOrientationsAttr().Set(self._gf_orientations)
def set_scales(self, scales: np.ndarray | torch.Tensor, indices: Sequence[int] | None = None):
"""Update marker poses in the simulation world frame.
Args:
scales: Scale applied before any rotation is applied. Shape is (M, 3).
indices: Indices to specify which alter poses.
Shape is (M,), where M <= total number of markers. Defaults to None (i.e: all markers).
"""
# default arguments
if indices is None:
indices = range(self.count)
# resolve inputs
scales = scales.tolist()
# change marker locations
for i, marker_index in enumerate(indices):
self._gf_scales[marker_index][:] = scales[i]
# apply to instance manager
self._instancer_manager.GetScalesAttr().Set(self._gf_scales)
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
Camera wrapper around USD camera prim to provide an interface that follows the robotics convention.
"""
from .camera import Camera, CameraData
from .camera_cfg import FisheyeCameraCfg, PinholeCameraCfg
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Configuration for the camera sensor."""
from __future__ import annotations
from dataclasses import MISSING
from omni.isaac.orbit.utils import configclass
@configclass
class PinholeCameraCfg:
"""Configuration for a pinhole camera sensor."""
@configclass
class UsdCameraCfg:
"""USD related configuration of the sensor.
The parameter is kept default from USD if it is set to None. This includes the default
parameters (in case the sensor is created) or the ones set by the user (in case the sensor is
loaded from existing USD stage).
Reference:
* https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_replicator/annotators_details.html
* https://graphics.pixar.com/usd/docs/api/class_usd_geom_camera.html
"""
clipping_range: tuple[float, float] = None
"""Near and far clipping distances (in stage units)."""
focal_length: float = None
"""Perspective focal length (in mm). Longer lens lengths narrower FOV, shorter lens lengths wider FOV."""
focus_distance: float = None
"""Distance from the camera to the focus plane (in stage units).
The distance at which perfect sharpness is achieved.
"""
f_stop: float = None
"""Lens aperture. Defaults to 0.0, which turns off focusing.
Controls Distance Blurring. Lower Numbers decrease focus range, larger numbers increase it.
"""
horizontal_aperture: float = None
"""Horizontal aperture (in mm). Emulates sensor/film width on a camera."""
horizontal_aperture_offset: float = None
"""Offsets Resolution/Film gate horizontally."""
vertical_aperture_offset: float = None
"""Offsets Resolution/Film gate vertically."""
sensor_tick: float = 0.0
"""Simulation seconds between sensor buffers. Defaults to 0.0."""
data_types: list[str] = ["rgb"]
"""List of sensor names/types to enable for the camera. Defaults to ["rgb"]."""
width: int = MISSING
"""Width of the image in pixels."""
height: int = MISSING
"""Height of the image in pixels."""
semantic_types: list[str] = ["class"]
"""List of allowed semantic types the types. Defaults to ["class"].
For example, if semantic types is [“class”], only the bounding boxes for prims with semantics of
type “class” will be retrieved.
More information available at:
https://docs.omniverse.nvidia.com/app_code/prod_extensions/ext_replicator/semantic_schema_editor.html
"""
projection_type: str = "pinhole"
"""Type of projection to use for the camera. Defaults to "pinhole"."""
usd_params: UsdCameraCfg = UsdCameraCfg()
"""Parameters for setting USD camera settings."""
@configclass
class FisheyeCameraCfg(PinholeCameraCfg):
"""Configuration for a fisheye camera sensor."""
@configclass
class UsdCameraCfg(PinholeCameraCfg.UsdCameraCfg):
"""USD related configuration of the sensor for the fisheye model."""
fisheye_nominal_width: float = None
"""Nominal width of fisheye lens model."""
fisheye_nominal_height: float = None
"""Nominal height of fisheye lens model."""
fisheye_optical_centre_x: float = None
"""Horizontal optical centre position of fisheye lens model."""
fisheye_optical_centre_y: float = None
"""Vertical optical centre position of fisheye lens model."""
fisheye_max_fov: float = None
"""Maximum field of view of fisheye lens model."""
fisheye_polynomial_a: float = None
"""First component of fisheye polynomial."""
fisheye_polynomial_b: float = None
"""Second component of fisheye polynomial."""
fisheye_polynomial_c: float = None
"""Third component of fisheye polynomial."""
fisheye_polynomial_d: float = None
"""Fourth component of fisheye polynomial."""
fisheye_polynomial_e: float = None
"""Fifth component of fisheye polynomial."""
projection_type: str = "fisheye_polynomial"
"""Type of projection to use for the camera. Defaults to "fisheye_polynomial"."""
usd_params: UsdCameraCfg = UsdCameraCfg()
"""Parameters for setting USD camera settings."""
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
Height-scanner based on ray-casting operations using PhysX ray-caster.
"""
from .height_scanner import HeightScanner, HeightScannerData
from .height_scanner_cfg import HeightScannerCfg
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Configuration for the height scanner sensor."""
from __future__ import annotations
from dataclasses import MISSING
from omni.isaac.orbit.utils import configclass
@configclass
class HeightScannerCfg:
"""Configuration for the height-scanner sensor."""
sensor_tick: float = 0.0
"""Simulation seconds between sensor buffers. Defaults to 0.0."""
points: list = MISSING
"""The 2D scan points to query ray-casting from. Results are reported in this order."""
offset: tuple[float, float, float] = (0.0, 0.0, 0.0)
"""The offset from the frame the sensor is attached to. Defaults to (0.0, 0.0, 0.0)."""
direction: tuple[float, float, float] = (0.0, 0.0, -1.0)
"""Unit direction for the scanner ray-casting. Defaults to (0.0, 0.0, -1.0)."""
max_distance: float = 100.0
"""Maximum distance from the sensor to ray cast to. Defaults to 100.0."""
filter_prims: list[str] = list()
"""A list of prim names to ignore ray-cast collisions with. Defaults to empty list."""
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Helper class to handle visual sphere markers to show ray-casting of height scanner."""
from __future__ import annotations
import numpy as np
import torch
from collections.abc import Sequence
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from pxr import Gf, UsdGeom
class HeightScannerMarker:
"""Helper class to handle visual sphere markers to show ray-casting of height scanner.
The class uses :class:`UsdGeom.PointInstancer` for efficient handling of multiple markers in the stage.
It creates two spherical markers of different colors. Based on the indices provided the referenced
marker is activated.
The status marker (proto-indices) of the point instancer is used to store the following information:
- :obj:`0` -> ray miss (blue sphere).
- :obj:`1` -> successful ray hit (red sphere).
- :obj:`2` -> invisible ray (disabled visualization)
"""
def __init__(self, prim_path: str, count: int, radius: float = 1.0) -> None:
"""Initialize the class.
Args:
prim_path: The prim path of the point instancer.
count: The number of markers to create.
radius: The radius of the spherical markers. Defaults to 1.0.
Raises:
ValueError: When a prim at the given path exists but is not a valid point instancer.
"""
# check inputs
stage = stage_utils.get_current_stage()
# -- prim path
if prim_utils.is_prim_path_valid(prim_path):
prim = prim_utils.get_prim_at_path(prim_path)
if not prim.IsA(UsdGeom.PointInstancer):
raise ValueError(f"The prim at path {prim_path} cannot be parsed as a `PointInstancer` object")
self._instancer_manager = UsdGeom.PointInstancer(prim)
else:
self._instancer_manager = UsdGeom.PointInstancer.Define(stage, prim_path)
# store inputs
self.prim_path = prim_path
self.count = count
self._radius = radius
# create manager for handling instancing of frame markers
self._instancer_manager = UsdGeom.PointInstancer.Define(stage, prim_path)
# TODO: Make this generic marker for all and put inside the `omni.isaac.orbit.marker` directory.
# create a child prim for the marker
# -- target missed
prim = prim_utils.create_prim(f"{prim_path}/point_miss", "Sphere", attributes={"radius": self._radius})
geom = UsdGeom.Sphere(prim)
geom.GetDisplayColorAttr().Set([(0.0, 0.0, 1.0)])
# -- target achieved
prim = prim_utils.create_prim(f"{prim_path}/point_hit", "Sphere", attributes={"radius": self._radius})
geom = UsdGeom.Sphere(prim)
geom.GetDisplayColorAttr().Set([(1.0, 0.0, 0.0)])
# -- target invisible
prim = prim_utils.create_prim(f"{prim_path}/point_invisible", "Sphere", attributes={"radius": self._radius})
geom = UsdGeom.Sphere(prim)
geom.GetDisplayColorAttr().Set([(0.0, 0.0, 1.0)])
prim_utils.set_prim_visibility(prim, visible=False)
# add child reference to point instancer
relation_manager = self._instancer_manager.GetPrototypesRel()
relation_manager.AddTarget(f"{prim_path}/point_miss") # target index: 0
relation_manager.AddTarget(f"{prim_path}/point_hit") # target index: 1
relation_manager.AddTarget(f"{prim_path}/point_invisible") # target index: 2
# buffers for storing data in pixar Gf format
# TODO: Make them very far away from the scene?
self._proto_indices = [2] * self.count
self._gf_positions = [Gf.Vec3f(0.0, 0.0, -10.0) for _ in range(self.count)]
self._gf_orientations = [Gf.Quath() for _ in range(self.count)]
# specify that all initial prims are related to same geometry
self._instancer_manager.GetProtoIndicesAttr().Set(self._proto_indices)
# set initial positions of the targets
self._instancer_manager.GetPositionsAttr().Set(self._gf_positions)
self._instancer_manager.GetOrientationsAttr().Set(self._gf_orientations)
def set_visibility(self, visible: bool):
"""Sets the visibility of the markers.
The method does this through the USD API.
Args:
visible: flag to set the visibility.
"""
imageable = UsdGeom.Imageable(self._instancer_manager)
if visible:
imageable.MakeVisible()
else:
imageable.MakeInvisible()
def set_world_poses(
self,
positions: np.ndarray | torch.Tensor | None = None,
orientations: np.ndarray | torch.Tensor | None = None,
indices: Sequence[int] | None = None,
):
"""Update marker poses in the simulation world frame.
Args:
positions:
Positions in the world frame. Shape is (M, 3). Defaults to None, which means left unchanged.
orientations:
Quaternion orientations (w, x, y, z) in the world frame of the prims. Shape is (M, 4).
Defaults to None, which means left unchanged.
indices: Indices to specify which alter poses.
Shape is (M,), where M <= total number of markers. Defaults to None (i.e: all markers).
"""
# resolve inputs
if positions is not None:
positions = positions.tolist()
if orientations is not None:
orientations = orientations.tolist()
if indices is None:
indices = range(self.count)
# change marker locations
for i, marker_index in enumerate(indices):
if positions is not None:
self._gf_positions[marker_index][:] = positions[i]
if orientations is not None:
self._gf_orientations[marker_index].SetReal(orientations[i][0])
self._gf_orientations[marker_index].SetImaginary(orientations[i][1:])
# apply to instance manager
self._instancer_manager.GetPositionsAttr().Set(self._gf_positions)
self._instancer_manager.GetOrientationsAttr().Set(self._gf_orientations)
def set_status(self, status: list[int] | np.ndarray | torch.Tensor, indices: Sequence[int] | None = None):
"""Updates the marker activated by the instance manager.
Args:
status: Decides which prototype marker to visualize. Shape is (M)
indices: Indices to specify which alter poses. Shape is (M,), where M <= total number of markers.
Defaults to None (i.e: all markers).
"""
# default values
if indices is None:
indices = range(self.count)
# resolve input
if status is not list:
proto_indices = status.tolist()
else:
proto_indices = status
# change marker locations
for i, marker_index in enumerate(indices):
self._proto_indices[marker_index] = int(proto_indices[i])
# apply to instance manager
self._instancer_manager.GetProtoIndicesAttr().Set(self._proto_indices)
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Utilities to create and visualize 2D height-maps."""
from __future__ import annotations
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.axes import Axes
from matplotlib.image import AxesImage
def create_points_from_grid(size: tuple[float, float], resolution: float) -> np.ndarray:
"""Creates a list of points from 2D mesh-grid.
The terrain scan is approximated with a grid map of the input resolution.
By default, we consider the origin as the center of the local map and the scan size ``(X, Y)`` is the
map size. Given these settings, the elevation map spans from: ``(- X / 2, - Y / 2)`` to
``(+ X / 2, + Y / 2)``.
Example:
For a grid of size (0.2, 0.2) with resolution of 0.1, the created points will first x-axis fixed, while the
y-axis changes, i.e.:
.. code-block:: none
[
[-0.1, -0.1], [-0.1, 0.0], [-0.1, 0.1],
[0.0, -0.1], [0.0, 0.], [0.0, 0.1],
[0.1, -0.1], [0.1, 0.0], [0.1, 0.1],
]
Args:
size: The 2D scan region along x and y directions (in meters).
resolution: The resolution of the scanner (in meters/cell).
Returns:
A set of points of shape (N, 2) or (N, 3), where first x is fixed while y changes.
"""
# Compute the scan grid
# Note: np.arange does not include end-point when dealing with floats. That is why we add resolution.
x = np.arange(-size[0] / 2, size[0] / 2 + resolution, resolution)
y = np.arange(-size[1] / 2, size[1] / 2 + resolution, resolution)
grid = np.meshgrid(x, y, sparse=False, indexing="ij")
# Concatenate the scan grid into points array (N, 2): first x is fixed while y changes
return np.vstack(list(map(np.ravel, grid))).T
def plot_height_grid(
hit_distance: np.ndarray, size: tuple[float, float], resolution: float, ax: Axes = None
) -> AxesImage:
"""Plots the sensor height-map distances using matplotlib.
If the axes is not provided, a new figure is created.
Note:
This method currently only supports if the grid is evenly spaced, i.e. the scan points are created using
:meth:`create_points_from_grid` method.
Args:
hit_distance: The ray hit distance measured from the sensor.
size: The 2D scan region along x and y directions (in meters).
resolution: The resolution of the scanner (in meters/cell).
ax: The current matplotlib axes to plot in.. Defaults to None.
Returns:
Image axes of the created plot.
"""
# Check that request of keys has same length as available axes.
if ax is None:
# Create axes if not provided
# Setup a figure
_, ax = plt.subplots()
# turn axes off
ax.clear()
# resolve shape of the heightmap
x = np.arange(-size[0] / 2, size[0] / 2 + resolution, resolution)
y = np.arange(-size[1] / 2, size[1] / 2 + resolution, resolution)
shape = (len(x), len(y))
# convert the map shape
heightmap = hit_distance.reshape(shape)
# plot the scanned distance
caxes = ax.imshow(heightmap, cmap="turbo", interpolation="none", vmin=0)
# set the label
ax.set_xlabel("y (m)")
ax.set_ylabel("x (m)")
# set the ticks
ax.set_xticks(np.arange(shape[1]), minor=False)
ax.set_yticks(np.arange(shape[0]), minor=False)
ax.set_xticklabels([round(value, 2) for value in y])
ax.set_yticklabels([round(value, 2) for value in x])
# add grid
ax.grid(color="w", linestyle="--", linewidth=1)
# return the color axes
return caxes
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Base class for sensors.
This class defines an interface for sensors similar to how the :class:`omni.isaac.orbit.robot.robot_base.RobotBase` class works.
Each sensor class should inherit from this class and implement the abstract methods.
"""
from __future__ import annotations
from abc import abstractmethod
from typing import Any
from warnings import warn
import carb
class SensorBase:
"""The base class for implementing a sensor.
Note:
These sensors are not vectorized yet.
"""
def __init__(self, sensor_tick: float = 0.0):
"""Initialize the sensor class.
The sensor tick is the time between two sensor buffers. If the sensor tick is zero, then the sensor
buffers are filled at every simulation step.
Args:
sensor_tick: Simulation seconds between sensor buffers. Defaults to 0.0.
"""
# print warning to notify user that the sensor is not vectorized
carb.log_warn("This implementation of the sensor is not vectorized yet. Please use the vectorized version.")
# Copy arguments to class
self._sensor_tick: float = sensor_tick
# Current timestamp of animation (in seconds)
self._timestamp: float = 0.0
# Timestamp from last update
self._timestamp_last_update: float = 0.0
# Frame number when the measurement is taken
self._frame: int = 0
def __init_subclass__(cls, **kwargs):
"""This throws a deprecation warning on subclassing."""
warn(f"{cls.__name__} will be deprecated from v1.0.", DeprecationWarning, stacklevel=1)
super().__init_subclass__(**kwargs)
"""
Properties
"""
@property
def frame(self) -> int:
"""Frame number when the measurement took place."""
return self._frame
@property
def timestamp(self) -> float:
"""Simulation time of the measurement (in seconds)."""
return self._timestamp
@property
def sensor_tick(self) -> float:
"""Simulation seconds between sensor buffers (ticks)."""
return self._sensor_tick
@property
def data(self) -> Any:
"""The data from the simulated sensor."""
return None # noqa: R501
"""
Helpers
"""
def set_visibility(self, visible: bool):
"""Set visibility of the instance in the scene.
Note:
Sensors are mostly XForms which do not have any mesh associated to them. Thus,
overriding this method is optional.
Args:
visible: Whether to make instance visible or invisible.
"""
pass
"""
Operations
"""
@abstractmethod
def spawn(self, parent_prim_path: str):
"""Spawns the sensor into the stage.
Args:
parent_prim_path: The path of the parent prim to attach sensor to.
"""
raise NotImplementedError
@abstractmethod
def initialize(self):
"""Initializes the sensor handles and internal buffers."""
raise NotImplementedError
def reset(self):
"""Resets the sensor internals."""
# Set current time
self._timestamp = 0.0
self._timestamp_last_update = 0.0
# Set zero captures
self._frame = 0
def update(self, dt: float, *args, **kwargs):
"""Updates the buffers at sensor frequency.
This function performs time-based checks and fills the data into the data container. It
calls the function :meth:`buffer()` to fill the data. The function :meth:`buffer()` should
not be called directly.
Args:
dt: The simulation time-step.
args: Other positional arguments passed to function :meth:`buffer()`.
kwargs: Other keyword arguments passed to function :meth:`buffer()`.
"""
# Get current time
self._timestamp += dt
# Buffer the sensor data.
if (self._timestamp - self._timestamp_last_update) >= self._sensor_tick:
# Buffer the data
self.buffer(*args, **kwargs)
# Update the frame count
self._frame += 1
# Update capture time
self._timestamp_last_update = self._timestamp
@abstractmethod
def buffer(self, *args, **kwargs):
"""Fills the buffers of the sensor data.
This function does not perform any time-based checks and directly fills the data into the data container.
Warning:
Although this method is public, `update(dt)` should be the preferred way of filling buffers.
"""
raise NotImplementedError
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
from omni.isaac.kit import SimulationApp
# launch the simulator
config = {"headless": False}
simulation_app = SimulationApp(config)
"""Rest everything follows."""
import unittest
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.core.utils.stage as stage_utils
from omni.isaac.core.simulation_context import SimulationContext
from omni.isaac.core.utils.viewports import set_camera_view
import omni.isaac.orbit.compat.utils.kit as kit_utils
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
class TestKitUtilities(unittest.TestCase):
"""Test fixture for checking Kit utilities in Orbit."""
@classmethod
def tearDownClass(cls):
"""Closes simulator after running all test fixtures."""
simulation_app.close()
def setUp(self) -> None:
"""Create a blank new stage for each test."""
# Simulation time-step
self.dt = 0.1
# Load kit helper
self.sim = SimulationContext(physics_dt=self.dt, rendering_dt=self.dt, backend="numpy")
# Set camera view
set_camera_view(eye=[1.0, 1.0, 1.0], target=[0.0, 0.0, 0.0])
# Spawn things into stage
self._populate_scene()
# Wait for spawning
stage_utils.update_stage()
def tearDown(self) -> None:
"""Stops simulator after each test."""
# stop simulation
self.sim.stop()
self.sim.clear()
def test_rigid_body_properties(self):
"""Disable setting of rigid body properties."""
# create marker
prim_utils.create_prim(
"/World/marker", usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd"
)
# set marker properties
kit_utils.set_nested_rigid_body_properties("/World/marker", rigid_body_enabled=False)
kit_utils.set_nested_collision_properties("/World/marker", collision_enabled=False)
# play simulation
self.sim.reset()
for _ in range(5):
self.sim.step()
"""
Helper functions.
"""
@staticmethod
def _populate_scene():
"""Add prims to the scene."""
# Ground-plane
kit_utils.create_ground_plane("/World/defaultGroundPlane")
# Lights-1
prim_utils.create_prim("/World/Light/GreySphere", "SphereLight", translation=(4.5, 3.5, 10.0))
# Lights-2
prim_utils.create_prim("/World/Light/WhiteSphere", "SphereLight", translation=(-4.5, 3.5, 10.0))
if __name__ == "__main__":
unittest.main()
......@@ -9,9 +9,6 @@ TESTS_TO_SKIP = [
"test_argparser_launch.py", # app.close issue
"test_env_var_launch.py", # app.close issue
"test_kwarg_launch.py", # app.close issue
"compat/test_kit_utils.py", # Compat to be deprecated
"compat/sensors/test_height_scanner.py", # Compat to be deprecated
"compat/sensors/test_camera.py", # Timing out
"test_differential_ik.py", # Failing
# orbit_tasks
"test_data_collector.py", # Failing
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment