Skip to content

Commit

Permalink
Receptacle unique_name filtering (facebookresearch#1298)
Browse files Browse the repository at this point in the history
* refactor to use Receptacle unique_name for sample filtering. Add support for filter files configured in scene instance config user_defined fields.

* assert uniqueness of Receptacle names when loaded.
  • Loading branch information
aclegg3 authored May 18, 2023
1 parent cfb4dc8 commit 3b76eb0
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 17 deletions.
17 changes: 14 additions & 3 deletions habitat-lab/habitat/datasets/rearrange/rearrange_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,10 @@ def generate_single_episode(self) -> Optional[RearrangeEpisode]:
ep_scene_handle = self.generate_scene()
scene_base_dir = osp.dirname(osp.dirname(ep_scene_handle))

recep_tracker.init_scene_filters(
mm=self.sim.metadata_mediator, scene_handle=ep_scene_handle
)

scene_name = ep_scene_handle.split(".")[0]
navmesh_path = osp.join(
scene_base_dir, "navmeshes", scene_name + ".navmesh"
Expand All @@ -480,7 +484,11 @@ def generate_single_episode(self) -> Optional[RearrangeEpisode]:
all_target_receptacles = []
for sampler_name, num_targets in target_numbers.items():
new_target_receptacles: List[Receptacle] = []
failed_samplers: Dict[str, bool] = defaultdict(bool)
while len(new_target_receptacles) < num_targets:
assert len(failed_samplers.keys()) < len(
targ_sampler_name_to_obj_sampler_names[sampler_name]
), f"All target samplers failed to find a match for '{sampler_name}'."
obj_sampler_name = random.choice(
targ_sampler_name_to_obj_sampler_names[sampler_name]
)
Expand All @@ -492,7 +500,9 @@ def generate_single_episode(self) -> Optional[RearrangeEpisode]:
)
except AssertionError:
# No receptacle instances found matching this sampler's requirements, likely ran out of allocations and a different sampler should be tried
failed_samplers[obj_sampler_name]
continue

if recep_tracker.allocate_one_placement(new_receptacle):
# used up new_receptacle, need to recompute the sampler's receptacle_candidates
sampler.receptacle_candidates = None
Expand Down Expand Up @@ -526,7 +536,7 @@ def generate_single_episode(self) -> Optional[RearrangeEpisode]:

# Goal and target containing receptacles are allowed 1 extra maximum object for each goal/target if a limit was defined
for recep in [*all_goal_receptacles, *all_target_receptacles]:
recep_tracker.inc_count(recep.name)
recep_tracker.inc_count(recep.unique_name)

# sample AO states for objects in the scene
# ao_instance_handle -> [ (link_ix, state), ... ]
Expand Down Expand Up @@ -716,7 +726,8 @@ def extract_recep_info(recep):
]

name_to_receptacle = {
k: v.name for k, v in self.object_to_containing_receptacle.items()
k: v.unique_name
for k, v in self.object_to_containing_receptacle.items()
}

return RearrangeEpisode(
Expand Down Expand Up @@ -919,7 +930,7 @@ def settle_sim(
if obj_name in unstable_placements:
rec_num_obj_vs_unstable[rec]["num_unstable_objects"] += 1
for rec, obj_in_rec in rec_num_obj_vs_unstable.items():
detailed_receptacle_stability_report += f"\n receptacle '{rec.name}': ({obj_in_rec['num_unstable_objects']}/{obj_in_rec['num_objects']}) (unstable/total) objects."
detailed_receptacle_stability_report += f"\n receptacle '{rec.unique_name}': ({obj_in_rec['num_unstable_objects']}/{obj_in_rec['num_objects']}) (unstable/total) objects."

success = len(unstable_placements) == 0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def sample_receptacle(
for (
ex_receptacle_substr
) in receptacle_set.excluded_receptacle_substrings:
if ex_receptacle_substr in receptacle.name:
if ex_receptacle_substr in receptacle.unique_name:
culled = True
break
if culled:
Expand All @@ -154,7 +154,7 @@ def sample_receptacle(
for (
name_constraint
) in receptacle_set.included_receptacle_substrings:
if name_constraint in receptacle.name:
if name_constraint in receptacle.unique_name:
found_match = True
break
break
Expand All @@ -168,7 +168,7 @@ def sample_receptacle(
for (
name_constraint
) in receptacle_set.included_receptacle_substrings:
if name_constraint in receptacle.name:
if name_constraint in receptacle.unique_name:
# found a valid substring match for this receptacle, stop the search
found_match = True
break
Expand All @@ -193,7 +193,7 @@ def sample_receptacle(
if gravity_alignment < tilt_tolerance:
culled = True
logger.info(
f"Culled by tilt: '{receptacle.name}', {gravity_alignment}"
f"Culled by tilt: '{receptacle.unique_name}', {gravity_alignment}"
)
if not culled:
# found a valid receptacle
Expand Down Expand Up @@ -326,7 +326,7 @@ def sample_placement(
new_object.handle
)
logger.warning(
f"Failed to sample {object_handle} placement on {receptacle.name} in {self.max_placement_attempts} tries."
f"Failed to sample {object_handle} placement on {receptacle.unique_name} in {self.max_placement_attempts} tries."
)

return None
Expand Down Expand Up @@ -388,7 +388,7 @@ def single_sample(
else:
target_receptacle = self.sample_receptacle(sim, recep_tracker)
logger.info(
f"Sampling '{object_handle}' from '{target_receptacle.name}'"
f"Sampling '{object_handle}' from '{target_receptacle.unique_name}'"
)

new_object = self.sample_placement(
Expand Down
63 changes: 60 additions & 3 deletions habitat-lab/habitat/datasets/rearrange/samplers/receptacle.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import json
import os
import random
from abc import ABC, abstractmethod
Expand Down Expand Up @@ -57,6 +58,15 @@ def __init__(
self.parent_object_handle = parent_object_handle
self.parent_link = parent_link

# The unique name of this Receptacle instance in the current scene.
# This name is a combination of the object instance name and Receptacle name.
self.unique_name = ""
if self.parent_object_handle is None:
# this is a stage receptacle
self.unique_name = "stage|" + self.name
else:
self.unique_name = self.parent_object_handle + "|" + self.name

@property
def is_parent_object_articulated(self):
"""
Expand Down Expand Up @@ -766,6 +776,14 @@ def find_receptacles(
)
)

# check for non-unique naming mistakes in user dataset
for rec_ix in range(len(receptacles)):
rec1_unique_name = receptacles[rec_ix].unique_name
for rec_ix2 in range(rec_ix + 1, len(receptacles)):
assert (
rec1_unique_name != receptacles[rec_ix2].unique_name
), "Two Receptacles found with the same unique name '{rec1_unique_name}'. Likely indicates multiple receptacle entries with the same name in the same config."

return receptacles


Expand All @@ -787,7 +805,7 @@ def __init__(
receptacle_sets: Dict[str, ReceptacleSet],
):
"""
:param max_objects_per_receptacle: A Dict mapping receptacle names to the remaining number of objects allowed in the receptacle.
:param max_objects_per_receptacle: A Dict mapping receptacle unique names to the remaining number of objects allowed in the receptacle.
:param receptacle_sets: Dict mapping ReceptacleSet name to its dataclass.
"""
self._receptacle_counts: Dict[str, int] = max_objects_per_receptacle
Expand All @@ -800,10 +818,49 @@ def __init__(
def recep_sets(self) -> Dict[str, ReceptacleSet]:
return self._receptacle_sets

def init_scene_filters(
self, mm: habitat_sim.metadata.MetadataMediator, scene_handle: str
) -> None:
"""
Initialize the scene specific filter strings from metadata.
Looks for a filter file defined for the scene, loads filtered strings and adds them to the exclude list of all ReceptacleSets.
:param mm: The active MetadataMediator instance from which to load the filter data.
:param scene_handle: The handle of the currently instantiated scene.
"""
scene_user_defined = mm.get_scene_user_defined(scene_handle)
filtered_unique_names = []
if scene_user_defined is not None and scene_user_defined.has_value(
"scene_filter_file"
):
scene_filter_file = scene_user_defined.get("scene_filter_file")
# construct the dataset level path for the filter data file
scene_filter_file = os.path.join(
os.path.dirname(mm.active_dataset), scene_filter_file
)
with open(scene_filter_file, "r") as f:
filter_json = json.load(f)
for filter_type in [
"manually_filtered",
"access_filtered",
"stability_filtered",
"height_filtered",
]:
for filtered_unique_name in filter_json[filter_type]:
filtered_unique_names.append(filtered_unique_name)
# add exclusion filters to all receptacles sets
for _, r_set in self._receptacle_sets.items():
r_set.excluded_receptacle_substrings.extend(
filtered_unique_names
)
logger.debug(
f"Loaded receptacle filter data for scene '{scene_handle}' from configured filter file '{scene_filter_file}'."
)

def inc_count(self, recep_name: str) -> None:
"""
Increment allowed objects for a Receptacle.
:param recep_name: The name of the Receptacle.
:param recep_name: The unique name of the Receptacle.
"""
if recep_name in self._receptacle_counts:
self._receptacle_counts[recep_name] += 1
Expand All @@ -818,7 +875,7 @@ def allocate_one_placement(self, allocated_receptacle: Receptacle) -> bool:
:return: Whether or not the Receptacle has run out of remaining allocations.
"""
recep_name = allocated_receptacle.name
recep_name = allocated_receptacle.unique_name
if recep_name not in self._receptacle_counts:
return False
# decrement remaining allocations
Expand Down
5 changes: 0 additions & 5 deletions test/test_rearrange_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,11 @@
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import ctypes
import json
import os.path as osp
import sys
import time
from glob import glob

flags = sys.getdlopenflags()
sys.setdlopenflags(flags | ctypes.RTLD_GLOBAL)

import magnum as mn
import numpy as np
import pytest
Expand Down

0 comments on commit 3b76eb0

Please sign in to comment.