Semantic Annotations#
This exercise introduces creating custom SemanticAnnotations, connecting bodies with a free connection, and querying with the Entity Query Language (EQL).
Your goals:
First: Create Cap and Bottle semantic annotations and three cylinder bodies with exact sizes. Annotate them as described.
Second: Connect the cap and the large bottle under the world root using a Connection6DoF, positioning the cap perfectly on top of the bottle.
Third: Use EQL to query for all Bottle semantic_annotations that have a Cap assigned to them.
0. Setup#
1. Define semantic annotations and create/annotate bodies#
Your goals:
Define two custom dataclasses Bottle(SemanticAnnotation) and Cap(SemanticAnnotation).
A Cap is the semantic_annotation of a single body.
A Bottle is the semantic_annotation of a body and optionally a Cap.
Create one body with a smaller cylinder shape (height 2cm, width 3cm) as visual and collision; use it to create a Cap semantic_annotation.
Create a second body with a larger cylinder shape (height 30cm, width 8cm) as visual and collision; use it to create a Bottle semantic_annotation. Also assign the Cap semantic_annotation to the Bottle semantic_annotation.
Create a third body with a medium cylinder shape (height 15cm, width 4cm) as visual and collision; use it to create a Bottle semantic_annotation. Do not assign a Cap semantic_annotation to the Bottle semantic_annotation.
@dataclass(eq=False)
class Cap(SemanticAnnotation):
"""Semantic annotation declaring that a Body is a bottle cap."""
body: Body
@dataclass(eq=False)
class Bottle(SemanticAnnotation):
"""Semantic annotation declaring that a Body is a bottle; may reference a Cap."""
body: Body
cap: Optional[Cap] = field(default=None)
# Geometries (sizes are in meters)
cap_cylinder = Cylinder(width=0.03, height=0.02, origin=TransformationMatrix())
bottle_large_cylinder = Cylinder(width=0.08, height=0.30, origin=TransformationMatrix())
bottle_medium_cylinder = Cylinder(width=0.04, height=0.15, origin=TransformationMatrix())
# ShapeCollections
cap_shapes = ShapeCollection([cap_cylinder])
bottle_large_shapes = ShapeCollection([bottle_large_cylinder])
bottle_medium_shapes = ShapeCollection([bottle_medium_cylinder])
# Bodies
cap_body = Body(name=PrefixedName("cap_body"), collision=cap_shapes, visual=cap_shapes)
bottle_large_body = Body(
name=PrefixedName("bottle_large"), collision=bottle_large_shapes, visual=bottle_large_shapes
)
bottle_medium_body = Body(
name=PrefixedName("bottle_medium"), collision=bottle_medium_shapes, visual=bottle_medium_shapes
)
# Semantic annotations (not added to the world yet)
cap = Cap(body=cap_body)
bottle_large = Bottle(body=bottle_large_body, cap=cap)
bottle_medium = Bottle(body=bottle_medium_body)
2. Connect cap and large bottle under the root and place the cap on top#
Your goals:
Connect the cap body and the large bottle body with Connection6DoF connections under the world root.
Add the SemanticAnnotations and Connections to the world.
Use the exact cylinder parameters to place the cap perfectly on top of the bottle.
# Register bodies and annotations then create free connections under a dedicated root body
with world.modify_world():
world.add_semantic_annotation(cap)
world.add_semantic_annotation(bottle_large)
world.add_semantic_annotation(bottle_medium)
root_C_bottle_large = Connection6DoF(parent=virtual_root, child=bottle_large_body)
bottle_large_C_cap = Connection6DoF(parent=bottle_large_body, child=cap_body)
root_C_bottle_medium = Connection6DoF(parent=virtual_root, child=bottle_medium_body)
world.add_connection(root_C_bottle_large)
world.add_connection(bottle_large_C_cap)
world.add_connection(root_C_bottle_medium)
z_offset = bottle_large_cylinder.height / 2.0 + cap_cylinder.height / 2.0
cap_pose = TransformationMatrix.from_xyz_rpy(
z=z_offset
)
bottle_large_C_cap.origin = cap_pose
3. Query with EQL for Bottles that have a Cap#
Your goals:
Build an EQL query that returns all Bottle semantic_annotations in the world that have a Cap assigned.
Store the query in a variable named
bottles_with_cap_queryand the evaluated list inquery_result.
with symbolic_mode():
bottles_with_cap_query = an(
entity(
Bottle(cap=an(Cap())
)
)
)
query_result = list(bottles_with_cap_query.evaluate())
print(query_result)
[Bottle(name=PrefixedName(name='Bottle_2', prefix=None), body=Body(name=PrefixedName(name='bottle_large', prefix=None), index=1, collision_config=CollisionCheckingConfig(buffer_zone_distance=None, violated_distance=0.0, disabled=None, max_avoided_bodies=1), temp_collision_config=None), cap=Cap(name=PrefixedName(name='Cap_1', prefix=None), body=Body(name=PrefixedName(name='cap_body', prefix=None), index=2, collision_config=CollisionCheckingConfig(buffer_zone_distance=None, violated_distance=0.0, disabled=None, max_avoided_bodies=1), temp_collision_config=None)))]