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_query and the evaluated list in query_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)))]