-
Notifications
You must be signed in to change notification settings - Fork 8
Add planner grid cell sampler #147
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 | ||
|---|---|---|---|---|
|
|
@@ -356,3 +356,166 @@ def randomize_target_pose( | |||
|
|
||||
| target_poses = getattr(env, store_key) | ||||
| target_poses[env_ids] = pose | ||||
|
|
||||
|
|
||||
| class planner_grid_cell_sampler(Functor): | ||||
| """Sample grid cells for object placement without replacement. | ||||
|
|
||||
| This functor divides a planar region into a regular 2D grid and samples cells | ||||
| to place objects. Each sampled cell will be marked as occupied and will not be | ||||
| resampled until the grid is reset. | ||||
|
|
||||
| The sampler places objects at the center of selected grid cells, with the z-position | ||||
| set to a reference height. | ||||
| """ | ||||
|
|
||||
| def __init__(self, cfg: FunctorCfg, env: EmbodiedEnv): | ||||
| """Initialize the GridCellSampler functor. | ||||
|
|
||||
| Args: | ||||
| cfg: The configuration of the functor. | ||||
| env: The environment instance. | ||||
| """ | ||||
| super().__init__(cfg, env) | ||||
|
|
||||
| # Initialize the grid state (will be properly set in reset) | ||||
| self._grid_state: dict[int, torch.Tensor] = {} | ||||
| self._grid_cell_sizes: dict[int, tuple[float, float]] = {} | ||||
|
|
||||
| def reset(self, env_ids: Union[torch.Tensor, None] = None) -> None: | ||||
| """Reset the grid sampling state. | ||||
|
|
||||
| Args: | ||||
| env_ids: The environment IDs to reset. If None, resets all environments. | ||||
| """ | ||||
| if env_ids is None: | ||||
| env_ids = torch.arange(self._env.num_envs, device=self._env.device) | ||||
| elif not isinstance(env_ids, torch.Tensor): | ||||
| env_ids = torch.tensor(env_ids, device=self._env.device) | ||||
|
|
||||
| for env_id in env_ids: | ||||
| env_id_int = ( | ||||
| int(env_id.item()) if isinstance(env_id, torch.Tensor) else int(env_id) | ||||
| ) | ||||
| # Initialize grid as all zeros (all cells available) | ||||
| if env_id_int in self._grid_state: | ||||
| self._grid_state[env_id_int].fill_(0) | ||||
|
|
||||
| def __call__( | ||||
| self, | ||||
| env: EmbodiedEnv, | ||||
| env_ids: Union[torch.Tensor, None], | ||||
| position_range: tuple[list[float], list[float]], | ||||
| reference_height: float, | ||||
| object_uid_list: list[str], | ||||
| grid_size: tuple[int, int], | ||||
| physics_update_step: int = -1, | ||||
| ) -> None: | ||||
| """Sample grid cells and place objects at those positions. | ||||
|
|
||||
| Args: | ||||
| env: The environment instance. | ||||
| env_ids: The environment IDs to apply sampling. If None, applies to all environments. | ||||
| position_range: The planar range [(x_min, y_min), (x_max, y_max)] defining the region. | ||||
| reference_height: The z-coordinate for placing objects [m]. | ||||
| object_uid_list: List of rigid object UIDs to place in the grid cells. | ||||
| grid_size: A tuple (rows, cols) defining the grid dimensions. | ||||
| physics_update_step: The number of physics update steps to apply after placement. Default is -1 (no update). | ||||
|
|
||||
| Returns: | ||||
| None | ||||
| """ | ||||
| if env_ids is None: | ||||
| env_ids = torch.arange(env.num_envs, device=env.device) | ||||
| elif not isinstance(env_ids, torch.Tensor): | ||||
| env_ids = torch.tensor(env_ids, device=env.device) | ||||
|
|
||||
| self.reset(env_ids) | ||||
| num_instance = len(env_ids) | ||||
|
|
||||
| # Parse position range | ||||
| x_min, y_min = position_range[0] | ||||
| x_max, y_max = position_range[1] | ||||
| cols, rows = grid_size | ||||
yuecideng marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
|
|
||||
| obj_positions = [] | ||||
| obj_list = [] | ||||
| # Verify all objects exist | ||||
| for obj_uid in object_uid_list: | ||||
| if obj_uid not in env.sim.get_rigid_object_uid_list(): | ||||
| logger.log_warning( | ||||
| f"Object UID '{obj_uid}' not found in the simulation." | ||||
| ) | ||||
| continue | ||||
| obj_positions.append( | ||||
| torch.zeros((num_instance, 3), dtype=torch.float32, device=env.device) | ||||
| ) | ||||
| obj = env.sim.get_rigid_object(obj_uid) | ||||
| obj.reset() | ||||
| obj_list.append(obj) | ||||
|
|
||||
| # Calculate cell dimensions | ||||
| cell_width = (x_max - x_min) / cols | ||||
| cell_height = (y_max - y_min) / rows | ||||
|
|
||||
| # Initialize grid state for environments if not present | ||||
| for env_id in env_ids: | ||||
| env_id_int = ( | ||||
| int(env_id.item()) if isinstance(env_id, torch.Tensor) else int(env_id) | ||||
| ) | ||||
| if env_id_int not in self._grid_state: | ||||
| self._grid_state[env_id_int] = torch.zeros( | ||||
| rows, cols, device=env.device, dtype=torch.uint8 | ||||
| ) | ||||
| self._grid_cell_sizes[env_id_int] = (cell_width, cell_height) | ||||
|
||||
| self._grid_cell_sizes[env_id_int] = (cell_width, cell_height) |
Copilot
AI
Feb 25, 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.
If the grid has no available cells, the code breaks out of the per-object loop, leaving later objects in that env without a sampled position. Because obj_positions is initialized to zeros, those unplaced objects will later get a zero position applied (teleporting to origin). Track placement success per object/env and skip pose updates for unplaced entries (or preserve current pose).
Copilot
AI
Feb 25, 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.
obj_positions[obj_id] is shaped (len(env_ids), 3), but it’s indexed with env_id (the global env index). If env_ids isn’t 0..len(env_ids)-1 (e.g., [2, 5]), this will be out-of-bounds or write the wrong row. Index by the loop-local row (e.g., enumerate env_ids) or build an env_id→row mapping.
Copilot
AI
Feb 25, 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.
This assignment applies obj_positions[obj_id] to all env_ids for every object, even if some environments didn’t successfully sample a cell for that object (e.g., grid full and loop broke early). That can overwrite poses with default zeros. Consider masking the pose update per-env based on which placements succeeded.
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.
The docstring says cells “will not be resampled until the grid is reset”, but
__call__unconditionally callsself.reset(env_ids)here, clearing occupancy on every invocation. If the intent is persistent no-replacement across resets, remove this call and let the EventManager/caller triggerreset()explicitly.