Skip to content

Commit b473b4d

Browse files
pascal-rothrenezurbrueggjtigue-bdaiDavid Hoelleraravindev
authored
Adds live plots to managers (#893)
# Description This adds a UI interface to the Managers in the `ManagerBasedEnv` and The `MangerBasedRLEnv`. Additions include: - UI widgets for `LiveLinePlot` and `ImagePlot` - `ManagerLiveVisualizer/Cfg`: Given a `ManagerBase` (i.e. action_manager, observation_manager, etc) and a config file this class creates the the interface between managers and the UI. - `EnvLiveVisualizer`: A 'manager' of `ManagerLiveVisualizer`. This is added to the `ManagerBasedEnv` but is only called during the initialization of the managers in `load_managers` - Adds `get_active_iterable_terms` implementation methods to ActionManager, ObservationManager, CommandsManager, CurriculumManager, RewardManager, and TerminationManager. This method exports the active term data and labels for each manager and is called by ManagerLiveVisualizer. - Additions to `BaseEnvWindow` and `RLEnvWindow` to register `ManagerLiveVisualizer` UI interfaces for the chosen managers. ## Screenshots [Screencast from 09-06-2024 01:20:18 PM.webm](https://github.com/user-attachments/assets/3ef0191d-5446-41bd-b274-43d886fb2d70) ## Implementation ![image](https://github.com/user-attachments/assets/49bd5493-3311-4c5c-a87c-6bbcd76a60fe) ## Type of change - New feature (non-breaking change which adds functionality) ## Checklist - [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [x] 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 - [ ] 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 --------- Signed-off-by: jtigue-bdai <166445701+jtigue-bdai@users.noreply.github.com> Signed-off-by: Pascal Roth <57946385+pascal-roth@users.noreply.github.com> Signed-off-by: David Hoeller <dhoeller@nvidia.com> Co-authored-by: zrene <zrene@ethz.ch> Co-authored-by: James Tigue <jtigue@theaiinstitute.com> Co-authored-by: jtigue-bdai <166445701+jtigue-bdai@users.noreply.github.com> Co-authored-by: David Hoeller <dhoeller@nvidia.com> Co-authored-by: Aravind EV <aravindev@live.in> Co-authored-by: Kelly Guo <kellyg@nvidia.com> Co-authored-by: Kelly Guo <kellyguo123@hotmail.com>
1 parent f7b59b3 commit b473b4d

20 files changed

+1569
-11
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ repos:
3838
- id: pyupgrade
3939
args: ["--py310-plus"]
4040
# FIXME: This is a hack because Pytorch does not like: torch.Tensor | dict aliasing
41-
exclude: "source/extensions/omni.isaac.lab/omni/isaac/lab/envs/common.py"
41+
exclude: "source/extensions/omni.isaac.lab/omni/isaac/lab/envs/common.py|source/extensions/omni.isaac.lab/omni/isaac/lab/ui/widgets/image_plot.py"
4242
- repo: https://github.com/codespell-project/codespell
4343
rev: v2.2.6
4444
hooks:

source/extensions/omni.isaac.lab/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "0.28.0"
4+
version = "0.29.0"
55

66
# Description
77
title = "Isaac Lab framework for Robot Learning"

source/extensions/omni.isaac.lab/docs/CHANGELOG.rst

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
Changelog
22
---------
33

4+
0.29.0 (2024-12-15)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Added
8+
^^^^^
9+
10+
* Added UI interface to the Managers in the ManagerBasedEnv and MangerBasedRLEnv classes.
11+
* Added UI widgets for :class:`LiveLinePlot` and :class:`ImagePlot`.
12+
* Added ``ManagerLiveVisualizer/Cfg``: Given a ManagerBase (i.e. action_manager, observation_manager, etc) and a config file this class creates the the interface between managers and the UI.
13+
* Added :class:`EnvLiveVisualizer`: A 'manager' of ManagerLiveVisualizer. This is added to the ManagerBasedEnv but is only called during the initialization of the managers in load_managers
14+
* Added ``get_active_iterable_terms`` implementation methods to ActionManager, ObservationManager, CommandsManager, CurriculumManager, RewardManager, and TerminationManager. This method exports the active term data and labels for each manager and is called by ManagerLiveVisualizer.
15+
* Additions to :class:`BaseEnvWindow` and :class:`RLEnvWindow` to register ManagerLiveVisualizer UI interfaces for the chosen managers.
16+
17+
418
0.28.0 (2024-12-15)
519
~~~~~~~~~~~~~~~~~~~
620

@@ -93,7 +107,7 @@ Changed
93107

94108

95109
0.27.21 (2024-12-06)
96-
~~~~~~~~~~~~~~~~~~~
110+
~~~~~~~~~~~~~~~~~~~~
97111

98112
Fixed
99113
^^^^^

source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_env.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from omni.isaac.lab.managers import ActionManager, EventManager, ObservationManager, RecorderManager
1515
from omni.isaac.lab.scene import InteractiveScene
1616
from omni.isaac.lab.sim import SimulationContext
17+
from omni.isaac.lab.ui.widgets import ManagerLiveVisualizer
1718
from omni.isaac.lab.utils.timer import Timer
1819

1920
from .common import VecEnvObs
@@ -148,6 +149,8 @@ def __init__(self, cfg: ManagerBasedEnvCfg):
148149
# we need to do this here after all the managers are initialized
149150
# this is because they dictate the sensors and commands right now
150151
if self.sim.has_gui() and self.cfg.ui_window_class_type is not None:
152+
# setup live visualizers
153+
self.setup_manager_visualizers()
151154
self._window = self.cfg.ui_window_class_type(self, window_name="IsaacLab")
152155
else:
153156
# if no window, then we don't need to store the window
@@ -233,6 +236,14 @@ def load_managers(self):
233236
if self.__class__ == ManagerBasedEnv and "startup" in self.event_manager.available_modes:
234237
self.event_manager.apply(mode="startup")
235238

239+
def setup_manager_visualizers(self):
240+
"""Creates live visualizers for manager terms."""
241+
242+
self.manager_visualizers = {
243+
"action_manager": ManagerLiveVisualizer(manager=self.action_manager),
244+
"observation_manager": ManagerLiveVisualizer(manager=self.observation_manager),
245+
}
246+
236247
"""
237248
Operations - MDP.
238249
"""

source/extensions/omni.isaac.lab/omni/isaac/lab/envs/manager_based_rl_env.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from omni.isaac.version import get_version
1717

1818
from omni.isaac.lab.managers import CommandManager, CurriculumManager, RewardManager, TerminationManager
19+
from omni.isaac.lab.ui.widgets import ManagerLiveVisualizer
1920

2021
from .common import VecEnvStepReturn
2122
from .manager_based_env import ManagerBasedEnv
@@ -132,6 +133,18 @@ def load_managers(self):
132133
if "startup" in self.event_manager.available_modes:
133134
self.event_manager.apply(mode="startup")
134135

136+
def setup_manager_visualizers(self):
137+
"""Creates live visualizers for manager terms."""
138+
139+
self.manager_visualizers = {
140+
"action_manager": ManagerLiveVisualizer(manager=self.action_manager),
141+
"observation_manager": ManagerLiveVisualizer(manager=self.observation_manager),
142+
"command_manager": ManagerLiveVisualizer(manager=self.command_manager),
143+
"termination_manager": ManagerLiveVisualizer(manager=self.termination_manager),
144+
"reward_manager": ManagerLiveVisualizer(manager=self.reward_manager),
145+
"curriculum_manager": ManagerLiveVisualizer(manager=self.curriculum_manager),
146+
}
147+
135148
"""
136149
Operations - MDP
137150
"""

source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/base_env_window.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import omni.usd
1717
from pxr import PhysxSchema, Sdf, Usd, UsdGeom, UsdPhysics
1818

19+
from omni.isaac.lab.ui.widgets import ManagerLiveVisualizer
20+
1921
if TYPE_CHECKING:
2022
import omni.ui
2123

@@ -57,6 +59,9 @@ def __init__(self, env: ManagerBasedEnv, window_name: str = "IsaacLab"):
5759
*self.env.scene.articulations.keys(),
5860
]
5961

62+
# Listeners for environment selection changes
63+
self._ui_listeners: list[ManagerLiveVisualizer] = []
64+
6065
print("Creating window for environment.")
6166
# create window for UI
6267
self.ui_window = omni.ui.Window(
@@ -80,6 +85,10 @@ def __init__(self, env: ManagerBasedEnv, window_name: str = "IsaacLab"):
8085
self._build_viewer_frame()
8186
# create collapsable frame for debug visualization
8287
self._build_debug_vis_frame()
88+
with self.ui_window_elements["debug_frame"]:
89+
with self.ui_window_elements["debug_vstack"]:
90+
self._visualize_manager(title="Actions", class_name="action_manager")
91+
self._visualize_manager(title="Observations", class_name="observation_manager")
8392

8493
def __del__(self):
8594
"""Destructor for the window."""
@@ -200,9 +209,6 @@ def _build_debug_vis_frame(self):
200209
that has it implemented. If the element does not have a debug visualization implemented,
201210
a label is created instead.
202211
"""
203-
# import omni.isaac.ui.ui_utils as ui_utils
204-
# import omni.ui
205-
206212
# create collapsable frame for debug visualization
207213
self.ui_window_elements["debug_frame"] = omni.ui.CollapsableFrame(
208214
title="Scene Debug Visualization",
@@ -234,6 +240,26 @@ def _build_debug_vis_frame(self):
234240
if elem is not None:
235241
self._create_debug_vis_ui_element(name, elem)
236242

243+
def _visualize_manager(self, title: str, class_name: str) -> None:
244+
"""Checks if the attribute with the name 'class_name' can be visualized. If yes, create vis interface.
245+
246+
Args:
247+
title: The title of the manager visualization frame.
248+
class_name: The name of the manager to visualize.
249+
"""
250+
251+
if hasattr(self.env, class_name) and class_name in self.env.manager_visualizers:
252+
manager = self.env.manager_visualizers[class_name]
253+
if hasattr(manager, "has_debug_vis_implementation"):
254+
self._create_debug_vis_ui_element(title, manager)
255+
else:
256+
print(
257+
f"ManagerLiveVisualizer cannot be created for manager: {class_name}, has_debug_vis_implementation"
258+
" does not exist"
259+
)
260+
else:
261+
print(f"ManagerLiveVisualizer cannot be created for manager: {class_name}, Manager does not exist")
262+
237263
"""
238264
Custom callbacks for UI elements.
239265
"""
@@ -357,6 +383,9 @@ def _set_viewer_env_index_fn(self, model: omni.ui.SimpleIntModel):
357383
raise ValueError("Viewport camera controller is not initialized! Please check the rendering mode.")
358384
# store the desired env index, UI is 1-indexed
359385
vcc.set_view_env_index(model.as_int - 1)
386+
# notify additional listeners
387+
for listener in self._ui_listeners:
388+
listener.set_env_selection(model.as_int - 1)
360389

361390
"""
362391
Helper functions - UI building.
@@ -379,14 +408,30 @@ def _create_debug_vis_ui_element(self, name: str, elem: object):
379408
alignment=omni.ui.Alignment.LEFT_CENTER,
380409
tooltip=text,
381410
)
411+
has_cfg = hasattr(elem, "cfg") and elem.cfg is not None
412+
is_checked = False
413+
if has_cfg:
414+
is_checked = (hasattr(elem.cfg, "debug_vis") and elem.cfg.debug_vis) or (
415+
hasattr(elem, "debug_vis") and elem.debug_vis
416+
)
382417
self.ui_window_elements[f"{name}_cb"] = SimpleCheckBox(
383418
model=omni.ui.SimpleBoolModel(),
384419
enabled=elem.has_debug_vis_implementation,
385-
checked=elem.cfg.debug_vis if elem.cfg else False,
420+
checked=is_checked,
386421
on_checked_fn=lambda value, e=weakref.proxy(elem): e.set_debug_vis(value),
387422
)
388423
omni.isaac.ui.ui_utils.add_line_rect_flourish()
389424

425+
# Create a panel for the debug visualization
426+
if isinstance(elem, ManagerLiveVisualizer):
427+
self.ui_window_elements[f"{name}_panel"] = omni.ui.Frame(width=omni.ui.Fraction(1))
428+
if not elem.set_vis_frame(self.ui_window_elements[f"{name}_panel"]):
429+
print(f"Frame failed to set for ManagerLiveVisualizer: {name}")
430+
431+
# Add listener for environment selection changes
432+
if isinstance(elem, ManagerLiveVisualizer):
433+
self._ui_listeners.append(elem)
434+
390435
async def _dock_window(self, window_title: str):
391436
"""Docks the custom UI window to the property window."""
392437
# wait for the window to be created

source/extensions/omni.isaac.lab/omni/isaac/lab/envs/ui/manager_based_rl_env_window.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ def __init__(self, env: ManagerBasedRLEnv, window_name: str = "IsaacLab"):
3434
with self.ui_window_elements["main_vstack"]:
3535
with self.ui_window_elements["debug_frame"]:
3636
with self.ui_window_elements["debug_vstack"]:
37-
self._create_debug_vis_ui_element("commands", self.env.command_manager)
38-
self._create_debug_vis_ui_element("actions", self.env.action_manager)
37+
self._visualize_manager(title="Commands", class_name="command_manager")
38+
self._visualize_manager(title="Rewards", class_name="reward_manager")
39+
self._visualize_manager(title="Curriculum", class_name="curriculum_manager")
40+
self._visualize_manager(title="Termination", class_name="termination_manager")

source/extensions/omni.isaac.lab/omni/isaac/lab/managers/action_manager.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ def set_debug_vis(self, debug_vis: bool) -> bool:
106106
# check if debug visualization is supported
107107
if not self.has_debug_vis_implementation:
108108
return False
109+
109110
# toggle debug visualization objects
110111
self._set_debug_vis_impl(debug_vis)
111112
# toggle debug visualization handles
@@ -262,7 +263,26 @@ def has_debug_vis_implementation(self) -> bool:
262263
Operations.
263264
"""
264265

265-
def set_debug_vis(self, debug_vis: bool) -> bool:
266+
def get_active_iterable_terms(self, env_idx: int) -> Sequence[tuple[str, Sequence[float]]]:
267+
"""Returns the active terms as iterable sequence of tuples.
268+
269+
The first element of the tuple is the name of the term and the second element is the raw value(s) of the term.
270+
271+
Args:
272+
env_idx: The specific environment to pull the active terms from.
273+
274+
Returns:
275+
The active terms.
276+
"""
277+
terms = []
278+
idx = 0
279+
for name, term in self._terms.items():
280+
term_actions = self._action[env_idx, idx : idx + term.action_dim].cpu()
281+
terms.append((name, term_actions.tolist()))
282+
idx += term.action_dim
283+
return terms
284+
285+
def set_debug_vis(self, debug_vis: bool):
266286
"""Sets whether to visualize the action data.
267287
Args:
268288
debug_vis: Whether to visualize the action data.

source/extensions/omni.isaac.lab/omni/isaac/lab/managers/command_manager.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,26 @@ def has_debug_vis_implementation(self) -> bool:
296296
Operations.
297297
"""
298298

299-
def set_debug_vis(self, debug_vis: bool) -> bool:
299+
def get_active_iterable_terms(self, env_idx: int) -> Sequence[tuple[str, Sequence[float]]]:
300+
"""Returns the active terms as iterable sequence of tuples.
301+
302+
The first element of the tuple is the name of the term and the second element is the raw value(s) of the term.
303+
304+
Args:
305+
env_idx: The specific environment to pull the active terms from.
306+
307+
Returns:
308+
The active terms.
309+
"""
310+
311+
terms = []
312+
idx = 0
313+
for name, term in self._terms.items():
314+
terms.append((name, term.command[env_idx].cpu().tolist()))
315+
idx += term.command.shape[1]
316+
return terms
317+
318+
def set_debug_vis(self, debug_vis: bool):
300319
"""Sets whether to visualize the command data.
301320
302321
Args:

source/extensions/omni.isaac.lab/omni/isaac/lab/managers/curriculum_manager.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,40 @@ def compute(self, env_ids: Sequence[int] | None = None):
138138
state = term_cfg.func(self._env, env_ids, **term_cfg.params)
139139
self._curriculum_state[name] = state
140140

141+
def get_active_iterable_terms(self, env_idx: int) -> Sequence[tuple[str, Sequence[float]]]:
142+
"""Returns the active terms as iterable sequence of tuples.
143+
144+
The first element of the tuple is the name of the term and the second element is the raw value(s) of the term.
145+
146+
Args:
147+
env_idx: The specific environment to pull the active terms from.
148+
149+
Returns:
150+
The active terms.
151+
"""
152+
153+
terms = []
154+
155+
for term_name, term_state in self._curriculum_state.items():
156+
if term_state is not None:
157+
# deal with dict
158+
data = []
159+
160+
if isinstance(term_state, dict):
161+
# each key is a separate state to log
162+
for key, value in term_state.items():
163+
if isinstance(value, torch.Tensor):
164+
value = value.item()
165+
terms[term_name].append(value)
166+
else:
167+
# log directly if not a dict
168+
if isinstance(term_state, torch.Tensor):
169+
term_state = term_state.item()
170+
data.append(term_state)
171+
terms.append((term_name, data))
172+
173+
return terms
174+
141175
"""
142176
Helper functions.
143177
"""

0 commit comments

Comments
 (0)