-
Notifications
You must be signed in to change notification settings - Fork 76
Feature/nvblox next datagen slim #883
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
david-tingdahl-nvidia
wants to merge
4
commits into
main
Choose a base branch
from
feature/nvblox_next_datagen_slim
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
5afe2b1
Add datagen collection hooks to the evaluation runner
david-tingdahl-nvidia b363360
Add DatagenCollectorBase interface for datagen collectors
david-tingdahl-nvidia ebfa982
Datagen run manager owns its collectors; interface and hook cleanup
david-tingdahl-nvidia 282e47d
Datagen: use the env's native auto-reset and a no-op default collector
david-tingdahl-nvidia File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| # Copyright (c) 2025-2026, The Isaac Lab Arena Project Developers (https://github.com/isaac-sim/IsaacLab-Arena/blob/main/CONTRIBUTORS.md). | ||
| # All rights reserved. | ||
| # | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """Interfaces for datagen data collection driven by the evaluation runners. | ||
|
|
||
| DatagenRunManagerBase is the single object injected into eval_runner.main. It drives one | ||
| DatagenCollectorBase per job and keeps whatever run-level records it needs. The runners | ||
| drive both objects only through these interfaces. NoOpDatagenRunManager is the default | ||
| when none is injected, so the runners never special-case the no-collection path. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from abc import ABC, abstractmethod | ||
| from typing import Any, Literal | ||
|
|
||
| # How an episode ended. | ||
| EpisodeOutcome = Literal["success", "failure", "timeout"] | ||
|
|
||
|
|
||
| class DatagenCollectorBase(ABC): | ||
|
david-tingdahl-nvidia marked this conversation as resolved.
|
||
| """Interface the evaluation runners use to drive a datagen data collector. | ||
|
|
||
| Implementations record per-step data during a policy rollout (see rollout_policy in | ||
| policy_runner.py). The runner calls on_step after every env.step, on_episode_end when the | ||
| env resets between episodes, finalize when the rollout finishes, and close when the job is | ||
| done. How a collector is constructed and configured is up to the implementing package. The | ||
| runners depend only on this interface. | ||
| """ | ||
|
|
||
| @abstractmethod | ||
| def on_step(self, env: Any, obs: Any, actions: Any, step_idx: int) -> None: | ||
| """Record one frame after an env.step.""" | ||
|
|
||
| @abstractmethod | ||
| def on_episode_end(self, env: Any, outcome: EpisodeOutcome = "timeout") -> None: | ||
| """Flush the in-progress episode with the outcome that ended it. | ||
|
|
||
| Also the place to prepare the next episode's cameras (e.g. re-randomize their | ||
| placement). The runner calls this once the env has auto-reset, so any re-aimed poses | ||
| take effect over the next episode's leading frames, which the collector is expected | ||
| to drop. | ||
| """ | ||
|
|
||
| @abstractmethod | ||
| def finalize(self, env: Any | None = None) -> None: | ||
| """Flush any in-progress episode and stop recording. Idempotent.""" | ||
|
|
||
| @abstractmethod | ||
| def close(self, env: Any | None = None) -> None: | ||
| """Finalize, then release resources such as spawned cameras. Idempotent.""" | ||
|
|
||
|
|
||
| class DatagenRunManagerBase(ABC): | ||
| """Interface for the run-level datagen object injected into eval_runner.main. | ||
|
|
||
| on_job_finished runs while the job's env is still alive, so an implementation can release | ||
| per-job resources such as spawned cameras before the stage teardown and record whatever | ||
| run-level bookkeeping it needs. | ||
| """ | ||
|
|
||
| @abstractmethod | ||
| def start_run(self, eval_jobs_config: dict, description: str | None, device: str) -> None: | ||
| """Called once before any job runs.""" | ||
|
|
||
| @abstractmethod | ||
| def create_collector(self, job_name: str, datagen_job_dict: dict, env: Any) -> DatagenCollectorBase: | ||
| """Build, retain, and return the collector for one job's live env.""" | ||
|
|
||
| @abstractmethod | ||
| def on_job_finished(self, job: Any, env: Any) -> None: | ||
| """Called once per collecting job after its rollout, while the env is still alive.""" | ||
|
|
||
| @abstractmethod | ||
| def finish_run(self) -> None: | ||
| """Called once after all jobs ran.""" | ||
|
|
||
| def needs_cameras(self) -> bool: | ||
| """Whether collection requires the sim to start with camera support enabled.""" | ||
| return True | ||
|
|
||
| def prepare_env_cfg(self, env_cfg: Any) -> None: | ||
| """Adjust an env cfg before its env is built. Called once per job. | ||
|
|
||
| The default drops the env's metrics and their recorder terms so the collector's own | ||
| dataset is the only one written. | ||
| """ | ||
| if hasattr(env_cfg, "metrics"): | ||
| env_cfg.metrics = None | ||
| if hasattr(env_cfg, "recorders"): | ||
| env_cfg.recorders = None | ||
|
|
||
|
|
||
| class NoOpDatagenCollector(DatagenCollectorBase): | ||
| """Collector that records nothing, used when no datagen collection is configured.""" | ||
|
|
||
| def on_step(self, env: Any, obs: Any, actions: Any, step_idx: int) -> None: | ||
| pass | ||
|
|
||
| def on_episode_end(self, env: Any, outcome: EpisodeOutcome = "timeout") -> None: | ||
| pass | ||
|
|
||
| def finalize(self, env: Any | None = None) -> None: | ||
| pass | ||
|
|
||
| def close(self, env: Any | None = None) -> None: | ||
| pass | ||
|
|
||
|
|
||
| class NoOpDatagenRunManager(DatagenRunManagerBase): | ||
| """Run manager that collects nothing. eval_runner defaults to it when none is injected.""" | ||
|
|
||
| def start_run(self, eval_jobs_config: dict, description: str | None, device: str) -> None: | ||
| pass | ||
|
|
||
| def create_collector(self, job_name: str, datagen_job_dict: dict, env: Any) -> DatagenCollectorBase: | ||
| return NoOpDatagenCollector() | ||
|
|
||
| def on_job_finished(self, job: Any, env: Any) -> None: | ||
| pass | ||
|
|
||
| def finish_run(self) -> None: | ||
| pass | ||
|
|
||
| def needs_cameras(self) -> bool: | ||
| return False | ||
|
|
||
| def prepare_env_cfg(self, env_cfg: Any) -> None: | ||
| pass | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Warning — This flips the reset behavior for every Arena environment, not just datagen.
IsaacLabArenaManagerBasedRLEnvCfgis the shared base for all RL/eval envs, so every reset now triggers 5 extra RTX rerenders for everyone — a non-trivial per-reset cost on any camera-enabled run — even though the comment scopes the motivation entirely to datagen ("corrupting RGB/depth/flow during datagen").The blast-radius/conservative-defaults invariant is that new behavior built for a narrow use case should be opt-in rather than silently changing the default path. Could this instead be applied only on the datagen cfg (e.g. inside
prepare_env_cfg_for_datagen, which already mutates the cfg for exactly this purpose) rather than the global base?Also, the comment says "Force at least one RTX sensor refresh" but the value is
5— is 5 needed, or would a smaller number (1?) suffice? Worth a NOTE explaining why 5.