Skip to content
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

Receptacle unique_name filtering #1298

Merged
merged 4 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
aclegg3 marked this conversation as resolved.
Show resolved Hide resolved
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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these strings documented somewhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using the "user_defined" fields for this, so there is no official documentation. It is documented in the Receptacle filtering process and the tools used to generate the configs.
Once we have everything working for the dataset release we should add a doc page to the website about the entire Receptacle system.

):
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