-
Notifications
You must be signed in to change notification settings - Fork 8
Fix(record): save camera recording on episode reset #208
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -515,6 +515,15 @@ def _initialize_episode( | |||||||||||||||||||||||||||||||||||||||||
| env_ids=successful_env_ids.nonzero(as_tuple=True)[0], | ||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| # Save recorded camera data before resetting | ||||||||||||||||||||||||||||||||||||||||||
| if self.cfg.events and self.event_manager is not None: | ||||||||||||||||||||||||||||||||||||||||||
| from embodichain.lab.gym.envs.managers.record import record_camera_data | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| for mode_cfgs in self.event_manager._mode_functor_cfgs.values(): | ||||||||||||||||||||||||||||||||||||||||||
| for functor_cfg in mode_cfgs: | ||||||||||||||||||||||||||||||||||||||||||
| if isinstance(functor_cfg.func, record_camera_data): | ||||||||||||||||||||||||||||||||||||||||||
| functor_cfg.func.save_and_clear() | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+518
to
+525
|
||||||||||||||||||||||||||||||||||||||||||
| # Save recorded camera data before resetting | |
| if self.cfg.events and self.event_manager is not None: | |
| from embodichain.lab.gym.envs.managers.record import record_camera_data | |
| for mode_cfgs in self.event_manager._mode_functor_cfgs.values(): | |
| for functor_cfg in mode_cfgs: | |
| if isinstance(functor_cfg.func, record_camera_data): | |
| functor_cfg.func.save_and_clear() | |
| # Save recorded camera data before resetting. | |
| # Only clear the shared recorder buffer when all environments are being reset. | |
| if self.cfg.events and self.event_manager is not None: | |
| from embodichain.lab.gym.envs.managers.record import record_camera_data | |
| # A global reset occurs when env_ids is None, or when the number of | |
| # environments to process matches the total number of environments. | |
| if env_ids is None or len(env_ids_to_process) == self.num_envs: | |
| for mode_cfgs in self.event_manager._mode_functor_cfgs.values(): | |
| for functor_cfg in mode_cfgs: | |
| if isinstance(functor_cfg.func, record_camera_data): | |
| functor_cfg.func.save_and_clear() |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -83,6 +83,7 @@ def __init__(self, cfg: FunctorCfg, env: EmbodiedEnv): | |||||||||
| if getattr(env.sim, "is_rt_enabled", False): | ||||||||||
| env.add_camera_group_id(self.camera.group_id) | ||||||||||
|
|
||||||||||
| self._save_path = cfg.params.get("save_path", "./outputs/videos") | ||||||||||
| self._current_episode = 0 | ||||||||||
| self._frames: List[np.ndarray] = [] | ||||||||||
|
|
||||||||||
|
|
@@ -124,6 +125,21 @@ def _draw_frames_into_one_image(self, frames: torch.Tensor) -> torch.Tensor: | |||||||||
|
|
||||||||||
| return result | ||||||||||
|
|
||||||||||
| def save_and_clear(self) -> None: | ||||||||||
| """Save recorded frames as video and clear the buffer. | ||||||||||
|
|
||||||||||
| This method is called from :meth:`EmbodiedEnv._initialize_episode` to ensure | ||||||||||
| frames are saved before the episode is reset. This avoids the issue where the | ||||||||||
| final episode's frames are lost because the save previously relied on detecting | ||||||||||
| a reset inside :meth:`__call__`. | ||||||||||
| """ | ||||||||||
| if len(self._frames) > 0: | ||||||||||
| video_name = f"episode_{self._current_episode}_{self._name}" | ||||||||||
| images_to_video(self._frames, self._save_path, video_name, fps=20) | ||||||||||
|
|
||||||||||
| self._current_episode += 1 | ||||||||||
| self._frames = [] | ||||||||||
|
|
||||||||||
| def __call__( | ||||||||||
| self, | ||||||||||
| env: EmbodiedEnv, | ||||||||||
|
|
@@ -142,15 +158,6 @@ def __call__( | |||||||||
| max_env_num: int = 16, | ||||||||||
| save_path: str = "./outputs/videos", | ||||||||||
| ): | ||||||||||
|
||||||||||
| ): | |
| ): | |
| # Ensure per-call save_path overrides the instance-level setting used by save_and_clear | |
| self._save_path = save_path |
Copilot
AI
Mar 30, 2026
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.
max_env_num is intended to limit how many env frames are used, but the current tiling logic later computes num_frames using max(rgb.shape[0], max_env_num), which won’t cap anything when rgb.shape[0] > max_env_num. Consider switching that computation to min(...) so max_env_num reliably bounds the grid size and avoids unnecessary work.
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.
This change introduces new episode-finalization behavior (calling
save_and_clear()during resets), but there are no tests asserting that recordings are saved/cleared at the right time. GivenBaseEnv.step()resets only a subset of envs ondones, adding a unit test around_initialize_episode(reset_ids=...)would help prevent regressions (e.g., ensure save/clear is only triggered on full resets or the intended condition).