Beispiel #1
0
 def put(self, key: str, value: V) -> None:
     check_state(self._zip_file, "Must use zip key-value sink as a context manager")
     check_not_none(key)
     check_not_none(value)
     if key in self._keys:
         raise ValueError(
             "Zip-backed key-value sinks do not support duplicate puts on the "
             "same key"
         )
     self._keys.add(key)
     filename = self._filename_function(key)
     check_arg(isinstance(value, str) or isinstance(value, bytes))
     self._zip_file.writestr(filename, value)  # type: ignore
Beispiel #2
0
 def _internal_get(
     self, key: str, *, has_default_val: bool, default_val: Optional[V]
 ) -> Optional[V]:
     check_state(self._zip_file, "Must use zip key-value source as a context manager")
     check_not_none(key)
     filename = self._filename_function(key)
     try:
         # safe by check_state above
         zip_bytes = self._zip_file.read(filename)  # type: ignore
     except KeyError as e:
         if has_default_val:
             return default_val
         raise KeyError(
             f"Key '{key}' not found in zip key-value source backed by " f"{self.path}"
         ) from e
     return self._process_bytes(zip_bytes)
Beispiel #3
0
    def items(
        self, key_filter: Callable[[str], bool] = lambda x: True
    ) -> Iterator[Tuple[str, bytes]]:
        check_state(
            self.inp,
            "Need to enter TarGZipBytesLinearKeyValueSource as context "
            "manager before using it.",
        )

        def generator_function() -> Iterator[Tuple[str, bytes]]:
            # safe by check_state above
            for member in self.inp:  # type: ignore
                if member.isfile() and self.name_filter(member.name):
                    key = self.key_function(member.name)
                    if key and key_filter(key):
                        data = self.inp.extractfile(member)  # type: ignore
                        if data:
                            with data:
                                yield (key, data.read())
                        else:
                            raise IOError(f"Cannot read member {member} of {self}")

        return generator_function()
Beispiel #4
0
    def _enrich_common(
        self, perception_semantic_alignment: PerceptionSemanticAlignment
    ) -> Tuple[PerceptionSemanticAlignment, AbstractSet[SemanticNode]]:
        """
        Shared code between `enrich_during_learning` and `enrich_during_description`.
        """
        preprocessing_result = self._preprocess_scene(perception_semantic_alignment)

        preprocessed_perception_graph = preprocessing_result.perception_graph

        # This accumulates our output.
        match_to_score: List[Tuple[SemanticNode, float]] = []

        # In the case of objects only, we alter the perception graph once they
        # are recognized by replacing the matched portion of the graph with the
        # ObjectSemanticNodes.  We gather them as we match and do the replacement below.
        matched_objects: List[Tuple[SemanticNode, PerceptionGraphPatternMatch]] = []

        # We pull this out into a function because we do matching in two passes:
        # first against templates whose meanings we are sure of (=have lexicalized)
        # and then, if no match has been found, against those we are still learning.
        def match_template(
            *, concept: Concept, pattern: PerceptionGraphTemplate, score: float
        ) -> None:
            # try to see if (our model of) its semantics is present in the situation.
            matcher = pattern.graph_pattern.matcher(
                preprocessed_perception_graph,
                match_mode=MatchMode.NON_OBJECT,
                # debug_callback=self._debug_callback,
            )
            for match in matcher.matches(use_lookahead_pruning=True):
                # if there is a match, use that match to describe the situation.
                semantic_node_for_match = pattern_match_to_semantic_node(
                    concept=concept, pattern=pattern, match=match
                )
                match_to_score.append((semantic_node_for_match, score))
                # We want to replace object matches with their semantic nodes,
                # but we don't want to alter the graph while matching it,
                # so we accumulate these to replace later.
                if isinstance(concept, ObjectConcept):
                    matched_objects.append((semantic_node_for_match, match))
                # A template only has to match once; we don't care about finding additional matches.
                return

        # For each template whose semantics we are certain of (=have been added to the lexicon)
        for (concept, graph_pattern, score) in self._primary_templates():
            check_state(isinstance(graph_pattern, PerceptionGraphTemplate))
            if (
                preprocessed_perception_graph.dynamic
                == graph_pattern.graph_pattern.dynamic
            ):
                match_template(concept=concept, pattern=graph_pattern, score=score)
            else:
                logging.debug(
                    f"Unable to try and match {concept} to {preprocessed_perception_graph} "
                    f"because both patterns must be static or dynamic"
                )
        if not match_to_score:
            # Try to match against patterns being learned
            # only if no lexicalized pattern was matched.
            for (concept, graph_pattern, score) in self._fallback_templates():
                # we may have multiple pattern hypotheses for a single concept, in which case we only want to identify the concept once
                if not any(m[0].concept == concept for m in match_to_score):
                    match_template(concept=concept, pattern=graph_pattern, score=score)

        perception_graph_after_matching = perception_semantic_alignment.perception_graph

        # Replace any objects found
        def by_pattern_complexity(pair):
            _, pattern_match = pair
            return len(pattern_match.matched_pattern)

        matched_objects.sort(key=by_pattern_complexity, reverse=True)
        already_replaced: Set[ObjectPerception] = set()
        new_nodes: List[SemanticNode] = []
        for (matched_object_node, pattern_match) in matched_objects:
            root: ObjectPerception = _get_root_object_perception(
                pattern_match.matched_sub_graph._graph,  # pylint:disable=protected-access
                immutableset(
                    pattern_match.matched_sub_graph._graph.nodes,  # pylint:disable=protected-access
                    disable_order_check=True,
                ),
            )
            if root not in already_replaced:
                perception_graph_after_matching = replace_match_root_with_object_semantic_node(
                    object_semantic_node=cast(ObjectSemanticNode, matched_object_node),
                    current_perception=perception_graph_after_matching,
                    pattern_match=pattern_match,
                )
                already_replaced.add(root)
                new_nodes.append(matched_object_node)
            else:
                logging.info(
                    f"Matched pattern for {matched_object_node} "
                    f"but root object {root} already replaced."
                )
        if matched_objects:
            immutable_new_nodes = immutableset(new_nodes)
        else:
            immutable_new_nodes = immutableset(node for (node, _) in match_to_score)

        (
            perception_graph_after_post_processing,
            nodes_after_post_processing,
        ) = self._enrich_post_process(
            perception_graph_after_matching, immutable_new_nodes
        )

        return (
            perception_semantic_alignment.copy_with_updated_graph_and_added_nodes(
                new_graph=perception_graph_after_post_processing,
                new_nodes=nodes_after_post_processing,
            ),
            nodes_after_post_processing,
        )
Beispiel #5
0
    def perception_text(
        self, perception: PerceptualRepresentation[
            DevelopmentalPrimitivePerceptionFrame]
    ) -> str:
        """
        Turns a perception into a list of items in the perceptions frames.
        """
        output_text: List[str] = []

        check_state(
            len(perception.frames) in (1, 2),
            "Only know how to handle 1 or 2 frame "
            "perceptions for now",
        )

        perception_is_dynamic = len(perception.frames) > 1

        # first, we build an index of objects to their properties.
        # This will be used so that when we list the objects,
        # we can easily list their properties in brackets right after them.
        def extract_subject(prop: PropertyPerception) -> ObjectPerception:
            return prop.perceived_object

        first_frame_properties = _index_to_setmultidict(
            perception.frames[0].property_assertions, extract_subject)
        second_frame_properties = (_index_to_setmultidict(
            perception.frames[1].property_assertions, extract_subject)
                                   if perception_is_dynamic else
                                   immutablesetmultidict())

        # Next, we determine what objects persist between both frames
        # and which do not.
        first_frame_objects = perception.frames[0].perceived_objects
        second_frame_objects = (perception.frames[1].perceived_objects
                                if perception_is_dynamic else immutableset())
        static_objects = (
            first_frame_objects.intersection(second_frame_objects)
            if perception_is_dynamic else first_frame_objects)
        all_objects = first_frame_objects.union(second_frame_objects)

        # For objects, properties, and relations we will use arrows to indicate
        # when something beings or ceased to exist between frames.
        # Since the logic will be the same for all three types,
        # we pull it out into a function.
        def compute_arrow(
                item: Any, static_items: AbstractSet[Any],
                first_frame_items: AbstractSet[Any]) -> Tuple[str, str]:
            if item in static_items:
                # item doesn't change - no arrow
                return ("", "")
            elif item in first_frame_items:
                # item ceases to exist
                return ("", " ---> Ø")
            else:
                # item beings to exist in the second frame
                return ("Ø ---> ", "")

        # the logic for rendering objects, which will be used in the loop below.
        # This needs to be an inner function so it can access the frame property maps, etc.
        def render_object(obj: ObjectPerception) -> str:
            obj_text = f"<i>{obj.debug_handle}</i>"
            first_frame_obj_properties = first_frame_properties[obj]
            second_frame_obj_properties = second_frame_properties[obj]
            static_properties = (second_frame_obj_properties.intersection(
                first_frame_obj_properties) if second_frame_obj_properties else
                                 first_frame_obj_properties)

            # logic for rendering properties, for use in the loop below.
            def render_property(prop: PropertyPerception) -> str:
                (prop_prefix,
                 prop_suffix) = compute_arrow(prop, static_properties,
                                              first_frame_obj_properties)
                prop_string: str
                if isinstance(prop, HasColor):
                    prop_string = (
                        f'<span style="background-color: {prop.color}; '
                        f'color: {prop.color.inverse()}; border: 1px solid black;">'
                        f"color={prop.color.hex}</span>")
                elif isinstance(prop, HasBinaryProperty):
                    prop_string = prop.binary_property.handle
                else:
                    raise RuntimeError(f"Cannot render property: {prop}")

                return f"{prop_prefix}{prop_string}{prop_suffix}"

            all_properties: ImmutableSet[PropertyPerception] = immutableset(
                flatten(
                    [first_frame_obj_properties, second_frame_obj_properties]))
            prop_strings = [render_property(prop) for prop in all_properties]

            if prop_strings:
                return f"{obj_text}[{'; '.join(prop_strings)}]"
            else:
                return obj_text

        # Here we process the relations between the two scenes to determine all relations.
        # This has to be done before rending objects so we can use the PART_OF relation to order
        # the objects.
        first_frame_relations = perception.frames[0].relations
        second_frame_relations = (perception.frames[1].relations
                                  if perception_is_dynamic else immutableset())
        static_relations = (
            second_frame_relations.intersection(first_frame_relations)
            if perception_is_dynamic else first_frame_relations)
        all_relations = first_frame_relations.union(second_frame_relations)

        # Here we add the perceived objects to a NetworkX DiGraph with PART_OF relations being the
        # edges between objects. This allows us to do pre-order traversal of the Graph to make a
        # nested <ul></ul> for the objects rather than a flat list.
        graph = DiGraph()
        root = ObjectPerception("root", axes=WORLD_AXES)
        graph.add_node(root)
        expressed_relations = set()
        axis_to_object: Dict[GeonAxis, ObjectPerception] = {}

        for object_ in all_objects:
            graph.add_node(object_)
            graph.add_edge(root, object_)
            for axis in object_.axes.all_axes:
                axis_to_object[axis] = object_

        for relation_ in all_relations:
            if relation_.relation_type == PART_OF:
                graph.add_edge(relation_.second_slot, relation_.first_slot)
                if graph.has_edge(root, relation_.first_slot):
                    graph.remove_edge(root, relation_.first_slot)
                expressed_relations.add(relation_)

        # Next, we render objects, together with their properties, using preorder DFS Traversal
        # We also add in `In Region` relationships at this step for objects which have them.
        output_text.append(
            "\n\t\t\t\t\t<h5>Perceived Objects</h5>\n\t\t\t\t\t<ul>")
        visited = set()
        region_relations = immutableset(region for region in all_relations
                                        if region.relation_type == IN_REGION)

        # This loop doesn't quite get the tab spacing right. It could at the cost of increased
        # complexity. Would need to track the "depth" we are currently at.
        axis_info = perception.frames[0].axis_info

        def dfs_walk(node, depth=0):
            visited.add(node)
            if not node == root:
                (obj_prefix,
                 obj_suffix) = compute_arrow(node, static_objects,
                                             first_frame_objects)
                output_text.append(
                    f"\t" * (6 + depth) +
                    f"<li>{obj_prefix}{render_object(node)}{obj_suffix}<ul>")
                if node.geon:
                    output_text.append(
                        f"\t\t\t\t\t\t<li>Geon: {self._render_geon(node.geon, indent_dept=7)}</li>"
                    )
                # Handle Region Relations
                for region_relation in region_relations:
                    if region_relation.first_slot == node:
                        (relation_prefix, relation_suffix) = compute_arrow(
                            region_relation, static_relations,
                            first_frame_relations)
                        relation_str = self._render_relation(
                            axis_info, region_relation)
                        output_text.append(
                            f"\t\t\t\t\t\t<li>{relation_prefix}"
                            f"{relation_str}{relation_suffix}</li>")
                        expressed_relations.add(region_relation)
            for succ in graph.successors(node):
                if succ not in visited:
                    depth = depth + 6
                    dfs_walk(succ, depth)
                    depth = depth - 6
            output_text.append("\t" * (6 + depth) + f"</ul></li>")

        dfs_walk(root)
        output_text.append("\t\t\t\t\t</ul>")

        # Finally we render remaining relations between objects
        remaining_relations = immutableset(
            relation for relation in all_relations
            if relation not in expressed_relations)
        if remaining_relations:
            output_text.append(
                "\t\t\t\t\t<h5>Other Relations</h5>\n\t\t\t\t\t<ul>")
            for relation in remaining_relations:
                (relation_prefix,
                 relation_suffix) = compute_arrow(relation, static_relations,
                                                  first_frame_relations)
                single_size_relation: Optional[Tuple[
                    Any, str, Any]] = self._get_single_size_relation(
                        relation, all_relations)
                if single_size_relation:
                    relation_text = f"{single_size_relation[0]} {single_size_relation[1]} {single_size_relation[2]}"
                    size_output = f"\t\t\t\t\t\t<li>{relation_prefix}{relation_text}{relation_suffix}</li>"
                    if size_output not in output_text:
                        output_text.append(size_output)
                else:
                    output_text.append(
                        f"\t\t\t\t\t\t<li>{relation_prefix}{relation}{relation_suffix}</li>"
                    )
            output_text.append("\t\t\t\t\t</ul>")

        if perception.during:
            output_text.append("\t\t\t\t\t<h5>During the action</h5>")
            output_text.append(
                self._render_during(perception.during, indent_depth=5))

        if axis_info and axis_info.axes_facing:
            output_text.append(("\t\t\t\t\t<h5>Axis Facings</h5>"))
            output_text.append(("\t\t\t\t\t<ul>"))
            for object_ in axis_info.axes_facing:
                output_text.append(
                    f"\t\t\t\t\t\t<li>{object_.debug_handle} faced by:\n\t\t\t\t\t\t<ul>"
                )
                for axis in axis_info.axes_facing[object_]:
                    output_text.append(
                        f"\t\t\t\t\t\t\t<li>{axis} possessed by {axis_to_object[axis]}</li>"
                    )
                output_text.append("\t\t\t\t\t\t</ul>")
            output_text.append("\t\t\t\t\t</ul>")

        return "\n".join(output_text)
Beispiel #6
0
    def _enrich_common(
        self, perception_semantic_alignment: PerceptionSemanticAlignment
    ) -> Tuple[PerceptionSemanticAlignment, AbstractSet[SemanticNode]]:
        """
        Shared code between `enrich_during_learning` and `enrich_during_description`.
        """
        preprocessing_result = self._preprocess_scene(
            perception_semantic_alignment)

        preprocessed_perception_graph = preprocessing_result.perception_graph

        # This accumulates our output.
        match_to_score: List[Tuple[SemanticNode, float]] = []

        # In the case of objects only, we alter the perception graph once they
        # are recognized by replacing the matched portion of the graph with the
        # ObjectSemanticNodes.  We gather them as we match and do the replacement below.
        matched_objects: List[Tuple[SemanticNode,
                                    PerceptionGraphPatternMatch]] = []

        # We pull this out into a function because we do matching in two passes:
        # first against templates whose meanings we are sure of (=have lexicalized)
        # and then, if no match has been found, against those we are still learning.
        def match_template(*, concept: Concept,
                           pattern: PerceptionGraphTemplate,
                           score: float) -> None:
            matches_with_nodes = self._match_template(
                concept=concept,
                pattern=pattern.copy_with_temporal_scopes(ENTIRE_SCENE)
                if preprocessed_perception_graph.dynamic
                and not pattern.graph_pattern.dynamic else pattern,
                perception_graph=preprocessed_perception_graph,
            )
            # The template may have zero, one, or many matches, so we loop over the matches found
            # Note that, with the exception of the object learners,
            # some matches may be essentially identical to each other.
            # This is fine because the corresponding semantic nodes will be equal,
            # so they'll get thrown out when we take the immutable set of new nodes.
            for match_with_node in matches_with_nodes:
                (match, semantic_node_for_match) = match_with_node
                match_to_score.append((semantic_node_for_match, score))
                # We want to replace object matches with their semantic nodes,
                # but we don't want to alter the graph while matching it,
                # so we accumulate these to replace later.
                if isinstance(concept, ObjectConcept):
                    matched_objects.append((semantic_node_for_match, match))

            # For each template whose semantics we are certain of (=have been added to the lexicon)

        for (concept, graph_pattern, score) in self._primary_templates():
            check_state(isinstance(graph_pattern, PerceptionGraphTemplate))
            if (preprocessed_perception_graph.dynamic ==
                    graph_pattern.graph_pattern.dynamic):
                match_template(concept=concept,
                               pattern=graph_pattern,
                               score=score)
            elif (preprocessed_perception_graph.dynamic
                  and not graph_pattern.graph_pattern.dynamic):
                match_template(
                    concept=concept,
                    pattern=graph_pattern.copy_with_temporal_scopes(
                        ENTIRE_SCENE),
                    score=score,
                )
            else:
                logging.debug(
                    f"Unable to try and match {concept} to {preprocessed_perception_graph} "
                    f"because both patterns must be static or dynamic")
        if not match_to_score:
            # Try to match against patterns being learned
            # only if no lexicalized pattern was matched.
            for (concept, graph_pattern, score) in self._fallback_templates():
                # we may have multiple pattern hypotheses for a single concept, in which case we only want to identify the concept once
                if not any(m[0].concept == concept for m in match_to_score):
                    match_template(concept=concept,
                                   pattern=graph_pattern,
                                   score=score)

        perception_graph_after_matching = perception_semantic_alignment.perception_graph

        # Replace any objects found
        def by_pattern_complexity(pair):
            _, pattern_match = pair
            return pattern_match.matched_pattern.pattern_complexity()

        matched_objects.sort(key=by_pattern_complexity, reverse=True)
        already_replaced: Set[ObjectPerception] = set()
        new_nodes: List[SemanticNode] = []

        for (matched_object_node, pattern_match) in matched_objects:
            root: ObjectPerception = _get_root_object_perception(
                pattern_match.matched_sub_graph._graph,  # pylint:disable=protected-access
                immutableset(
                    pattern_match.matched_sub_graph._graph.nodes,  # pylint:disable=protected-access
                    disable_order_check=True,
                ),
            )
            if root not in already_replaced:
                try:
                    replacement_result = replace_match_with_object_graph_node(
                        matched_object_node=cast(ObjectSemanticNode,
                                                 matched_object_node),
                        current_perception=perception_graph_after_matching,
                        pattern_match=pattern_match,
                    )
                    perception_graph_after_matching = (
                        replacement_result.perception_graph_after_replacement)
                    already_replaced.update(  # type: ignore
                        replacement_result.removed_nodes)
                    new_nodes.append(matched_object_node)
                except networkx.exception.NetworkXError:
                    logging.info(f"Matched pattern for {matched_object_node} "
                                 f"contains nodes that are already replaced")
            else:
                logging.info(f"Matched pattern for {matched_object_node} "
                             f"but root object {root} already replaced.")
        # If objects, only include the replaced graph nodes in the enrichment
        if new_nodes and isinstance(new_nodes[0], ObjectSemanticNode):
            immutable_new_nodes = immutableset(new_nodes)
        else:
            immutable_new_nodes = immutableset(node
                                               for (node, _) in match_to_score)

        # Keep recursively enriching so we can capture plurals. Do it only if we matched objects in the scene.
        if new_nodes and isinstance(new_nodes[0], ObjectSemanticNode):
            rec = self._enrich_common(
                perception_semantic_alignment.
                copy_with_updated_graph_and_added_nodes(
                    new_graph=perception_graph_after_matching,
                    new_nodes=immutable_new_nodes,
                ))
            return rec[0], set(immutable_new_nodes).union(rec[1])

        (
            perception_graph_after_post_processing,
            nodes_after_post_processing,
        ) = self._enrich_post_process(perception_graph_after_matching,
                                      immutable_new_nodes)

        return (
            perception_semantic_alignment.
            copy_with_updated_graph_and_added_nodes(
                new_graph=perception_graph_after_post_processing,
                new_nodes=nodes_after_post_processing,
            ),
            nodes_after_post_processing,
        )
Beispiel #7
0
 def __exit__(self, exc_type, exc_val, exc_tb) -> None:
     check_state(self._zip_file)
     self._zip_file.close()  # type: ignore
     self._zip_file = None
Beispiel #8
0
 def keys(self) -> Optional[AbstractSet[str]]:
     check_state(self._zip_file, "Must use zip key-value source as a context manager")
     return self._keys