Exemple #1
0
def canonize_hierarchy(tree, altitudes, return_node_map=False):
    """
    Removes consecutive tree nodes with equal altitudes.

    The new tree is composed of the inner nodes :math:`n` of the input tree such that
    :math:`altitudes[n] \\neq altitudes[tree.parent(n)]` or :math:`n = tree.root(n)`.

    For example, applying this function to the result of :func:`~higra.bpt_canonical` on an edge weighted graph
    is the same as computing the :func:`~higra.quasi_flat_zone_hierarchy` of the same edge weighted graph.

    If :attr:`return_node_map` is ``True``, an extra array that maps any vertex index :math:`i` of the new tree,
    to the index of the corresponding vertex in the original tree is returned.

    :param tree: input tree
    :param altitudes: altitudes of the vertices of the tree
    :param return_node_map: if ``True``, also return the node map.
    :return: a tree (Concept :class:`~higra.CptHierarchy` if input tree already satisfied this concept)
             its node altitudes, and, if requested, its node map.
    """
    tree, node_map = hg.simplify_tree(tree,
                                      altitudes == altitudes[tree.parents()])
    new_altitudes = altitudes[node_map]
    if return_node_map:
        return tree, new_altitudes, node_map
    else:
        return tree, new_altitudes
Exemple #2
0
    def num_parents(bpt_tree, altitudes):
        # construct quasi flat zone hierarchy from input bpt
        tree, node_map = hg.simplify_tree(
            bpt_tree, altitudes == altitudes[bpt_tree.parents()])

        # determine inner nodes of the min tree, i.e. nodes of qfz having at least one node that is not a leaf
        num_children = tree.num_children(np.arange(tree.num_vertices()))
        num_children_leaf = np.zeros((tree.num_vertices(), ), dtype=np.int64)
        np.add.at(num_children_leaf, tree.parents()[:tree.num_leaves()], 1)
        inner_nodes = num_children != num_children_leaf

        # go back into bpt space
        inner_nodes_bpt = np.zeros((bpt_tree.num_vertices(), ), dtype=np.int64)
        inner_nodes_bpt[node_map] = inner_nodes
        inner_nodes = inner_nodes_bpt

        # count number of min tree inner nodes in the subtree rooted in the given node
        res = hg.accumulate_and_add_sequential(bpt_tree, inner_nodes,
                                               inner_nodes[:tree.num_leaves()],
                                               hg.Accumulators.sum)

        # add 1 to avoid having a zero measure in a minima
        res[bpt_tree.num_leaves():] = res[bpt_tree.num_leaves():] + 1

        return res
Exemple #3
0
def constrained_connectivity_hierarchy_strong_connection(graph, edge_weights):
    """
    Strongly constrained connectivity hierarchy based on the given edge weighted graph.

    Let :math:`X` be a set of vertices, the range of :math:`X` is the maximal weight of the edges linking two vertices inside :math:`X`.

    Let :math:`\\alpha` be a positive real number, a set of vertices :math:`X` is :math:`\\alpha`-connected, if for any two vertices
    :math:`i` and :math:`j` in :math:`X`, there exists a path from :math:`i` to :math:`j` in :math:`X` composed of edges of weights
    lower than or equal to :math:`\\alpha`.

    Let :math:`\\alpha`  be a positive real numbers, the :math:`\\alpha`-strongly connected components of the graph are
    the maximal :math:`\\alpha'`-connected sets of vertices with a range lower than or equal to :math:`\\alpha` with :math:`\\alpha'\leq\\alpha`.

    Finally, the strongly constrained connectivity hierarchy is defined as the hierarchy composed of all the
    :math:`\\alpha`- strongly connected components for all positive :math:`\\alpha`.

    The definition used follows the one given in:

        P. Soille,
        "Constrained connectivity for hierarchical image partitioning and simplification,"
        in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 30, no. 7, pp. 1132-1145, July 2008.
        doi: 10.1109/TPAMI.2007.70817

    The algorithm runs in time :math:`\mathcal{O}(n\log(n))` and proceeds by filtering a quasi-flat zone hierarchy (see :func:`~higra.quasi_flat_zones_hierarchy`)

    :param graph: input graph
    :param edge_weights: edge_weights: edge weights of the input graph
    :return: a tree (Concept :class:`~higra.CptHierarchy`) and its node altitudes
    """

    tree, altitudes = hg.quasi_flat_zone_hierarchy(graph, edge_weights)
    altitude_parents = altitudes[tree.parents()]

    # max edge weights inside each region
    lca_map = hg.attribute_lca_map(tree)
    max_edge_weights = np.zeros((tree.num_vertices(),), dtype=edge_weights.dtype)
    np.maximum.at(max_edge_weights, lca_map, edge_weights)
    max_edge_weights = hg.accumulate_and_max_sequential(tree,
                                                        max_edge_weights,
                                                        max_edge_weights[:tree.num_leaves()],
                                                        hg.Accumulators.max)

    # parent node can't be deleted
    altitude_parents[tree.root()] = max(altitudes[tree.root()], max_edge_weights[tree.root()])

    # nodes whith a range greater than the altitudes of their parent have to be deleted
    violated_constraints = max_edge_weights >= altitude_parents

    # the altitude of nodes with a range greater than their altitude but lower than the one of their parent must be changed
    reparable_node_indices = np.nonzero(
        np.logical_and(max_edge_weights > altitudes, max_edge_weights < altitude_parents))
    altitudes[reparable_node_indices] = max_edge_weights[reparable_node_indices]

    # final  result construction
    tree, node_map = hg.simplify_tree(tree, violated_constraints)
    altitudes = altitudes[node_map]
    hg.CptHierarchy.link(tree, graph)

    return tree, altitudes
Exemple #4
0
    def test_simplify_tree_with_leaves3(self):
        t = hg.Tree((7, 7, 8, 8, 8, 9, 9, 11, 10, 10, 11, 11))

        criterion = np.zeros(t.num_vertices(), dtype=np.bool)
        criterion[:9] = True
        new_tree, node_map = hg.simplify_tree(t,
                                              criterion,
                                              process_leaves=True)

        ref_tree = hg.Tree((1, 2, 2))
        self.assertTrue(hg.test_tree_isomorphism(new_tree, ref_tree))

        self.assertFalse(np.max(criterion[node_map]))
Exemple #5
0
    def test_simplify_tree_with_leaves(self):
        t = hg.Tree((7, 7, 8, 8, 8, 9, 9, 11, 10, 10, 11, 11))

        criterion = np.asarray((False, False, False, True, True, True, True,
                                False, True, False, True, False),
                               dtype=np.bool)

        new_tree, node_map = hg.simplify_tree(t,
                                              criterion,
                                              process_leaves=True)

        ref_tree = hg.Tree((4, 4, 5, 5, 5, 5))
        self.assertTrue(hg.test_tree_isomorphism(new_tree, ref_tree))

        self.assertFalse(np.max(criterion[node_map]))
Exemple #6
0
    def test_simplify_tree_propagate_category(self):
        g = hg.get_4_adjacency_implicit_graph((1, 6))
        vertex_values = np.asarray((1, 5, 4, 3, 3, 6), dtype=np.int32)
        tree, altitudes = hg.component_tree_max_tree(g, vertex_values)

        condition = np.asarray((False, False, False, False, False, False,
                                False, True, False, True, False), np.bool)

        new_tree, node_map = hg.simplify_tree(tree, condition)

        self.assertTrue(
            np.all(new_tree.parents() == (8, 7, 7, 8, 8, 6, 8, 8, 8)))
        self.assertTrue(np.all(node_map == (0, 1, 2, 3, 4, 5, 6, 8, 10)))
        self.assertTrue(new_tree.category() == hg.TreeCategory.ComponentTree)

        rec = hg.reconstruct_leaf_data(new_tree, altitudes[node_map])
        self.assertTrue(np.all(rec == (1, 4, 4, 1, 1, 6)))
Exemple #7
0
def canonize_hierarchy(tree, altitudes):
    """
    Removes consecutive tree nodes with equal altitudes.

    The new tree is composed of the inner nodes :math:`n` of the input tree such that
    :math:`altitudes[n] \\neq altitudes[tree.parent(n)]` or :math:`n = tree.root(n)`.

    For example, applying this function to the result of :func:`~higra.bpt_canonical` on an edge weighted graph
    is the same as computing the :func:`~higra.quasi_flat_zone_hierarchy` of the same edge weighted graph.

    :param tree: input tree
    :param altitudes: altitudes of the vertices of the tree
    :return: a tree (Concept :class:`~higra.CptHierarchy` if input tree already satisfied this concept)
            and its node altitudes
    """
    ctree, node_map = hg.simplify_tree(tree,
                                       altitudes == altitudes[tree.parents()])
    return ctree, altitudes[node_map]
Exemple #8
0
    def test_simplifyTreeWithLeaves(self):
        t = hg.Tree((8, 8, 9, 7, 7, 11, 11, 9, 10, 10, 12, 12, 12))

        criterion = np.asarray((False, True, True, False, False, False, False,
                                False, True, True, False, False),
                               dtype=np.bool)

        new_tree, node_map = hg.simplify_tree(t,
                                              criterion,
                                              process_leaves=True)

        self.assertTrue(new_tree.num_vertices() == 9)

        refp = np.asarray((6, 5, 5, 7, 7, 6, 8, 8, 8))
        self.assertTrue(np.all(refp == new_tree.parents()))

        refnm = np.asarray((0, 3, 4, 5, 6, 7, 10, 11, 12))
        self.assertTrue(np.all(refnm == node_map))
Exemple #9
0
    def test_simplify_tree(self):
        t = TestHierarchyCore.getTree()

        altitudes = np.asarray((0, 0, 0, 0, 0, 1, 2, 2))

        criterion = np.equal(altitudes, altitudes[t.parents()])

        new_tree, node_map = hg.simplify_tree(t, criterion)

        # for reference
        new_altitudes = altitudes[node_map]

        self.assertTrue(new_tree.num_vertices() == 7)

        refp = np.asarray((5, 5, 6, 6, 6, 6, 6))
        self.assertTrue(np.all(refp == new_tree.parents()))

        refnm = np.asarray((0, 1, 2, 3, 4, 5, 7))
        self.assertTrue(np.all(refnm == node_map))
Exemple #10
0
def constrained_connectivity_hierarchy_alpha_omega(graph, vertex_weights):
    """
    Alpha-omega constrained connectivity hierarchy based on the given vertex weighted graph.

    For  :math:`(i,j)` be an edge of the graph, we define :math:`w(i,j)=|w(i) - w(j)|`, the weight of this edge.
    Let :math:`X` be a set of vertices, the range of :math:`X` is the maximal absolute difference between the weights of any two vertices in :math:`X`:
    :math:`R(X) = \max\{|w(i) - w(j)|, (i,j)\in X^2\}`

    Let :math:`\\alpha` be a positive real number, a set of vertices :math:`X` is :math:`\\alpha`-connected, if for any two vertices
    :math:`i` and :math:`j` in :math:`X`, there exists a path from :math:`i` to :math:`j` in :math:`X` composed of edges of weights
    lower than or equal to :math:`\\alpha`.

    Let :math:`\\alpha`  and :math:`\omega` be a two positive real numbers, the :math:`\\alpha-\omega`-connected components of the graph are
    the maximal :math:`\\alpha'`-connected sets of vertices with a range lower than or equal to :math:`\omega`, with :math:`\\alpha'\leq\\alpha`.

    Finally, the alpha-omega constrained connectivity hierarchy is defined as the hierarchy composed of all the :math:`k-k`-connected components for all positive :math:`k`.

    The definition used follows the one given in:

        P. Soille,
        "Constrained connectivity for hierarchical image partitioning and simplification,"
        in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 30, no. 7, pp. 1132-1145, July 2008.
        doi: 10.1109/TPAMI.2007.70817

    The algorithm runs in time :math:`\mathcal{O}(n\log(n))` and proceeds by filtering a quasi-flat zone hierarchy (see :func:`~higra.quasi_flat_zones_hierarchy`)

    :param graph: input graph
    :param vertex_weights: edge_weights: edge weights of the input graph
    :return: a tree (Concept :class:`~higra.CptHierarchy`) and its node altitudes
    """

    vertex_weights = hg.linearize_vertex_weights(vertex_weights, graph)
    if vertex_weights.ndim != 1:
        raise ValueError("constrainted_connectivity_hierarchy_alpha_omega only works for scalar vertex weights.")

    # QFZ on the L1 distance weighted graph
    edge_weights = hg.weight_graph(graph, vertex_weights, hg.WeightFunction.L1)
    tree, altitudes = hg.quasi_flat_zone_hierarchy(graph, edge_weights)
    altitude_parents = altitudes[tree.parents()]

    # vertex value range inside each region
    min_value = hg.accumulate_sequential(tree, vertex_weights, hg.Accumulators.min)
    max_value = hg.accumulate_sequential(tree, vertex_weights, hg.Accumulators.max)
    value_range = max_value - min_value

    # parent node can't be deleted
    altitude_parents[tree.root()] = max(altitudes[tree.root()], value_range[tree.root()])

    # nodes whith a range greater than the altitudes of their parent have to be deleted
    violated_constraints = value_range >= altitude_parents

    # the altitude of nodes with a range greater than their altitude but lower than the one of their parent must be changed
    reparable_node_indices = np.nonzero(np.logical_and(value_range > altitudes, value_range < altitude_parents))
    altitudes[reparable_node_indices] = value_range[reparable_node_indices]

    # final  result construction
    tree, node_map = hg.simplify_tree(tree, violated_constraints)
    altitudes = altitudes[node_map]
    hg.CptHierarchy.link(tree, graph)

    return tree, altitudes
Exemple #11
0
def component_tree_multivariate_tree_of_shapes_image2d(image,
                                                       padding='mean',
                                                       original_size=True,
                                                       immersion=True):
    """
    Multivariate tree of shapes for a 2d multi-band image. This tree is defined as a fusion of the marginal
    trees of shapes. The method is described in:

        E. Carlinet.
        `A Tree of shapes for multivariate images <https://pastel.archives-ouvertes.fr/tel-01280131/file/TH2015PESC1118.pdf>`_.
        PhD Thesis, Université Paris-Est, 2015.

    The input :attr:`image` must be a 3d array of shape :math:`(height, width, channel)`.

    Note that the constructed hierarchy doesn't have natural altitudes associated to its node: as a node is generally
    a fusion of several marginal nodes, we can't associate a single canonical value from the original image to this node.

    The parameters :attr:`padding`, :attr:`original_size`, and :attr:`immersion` are forwarded to the function
    :func:`~higra.component_tree_tree_of_shapes_image2d`: please look at this function documentation for more details.

    :Complexity:

    The worst case runtime complexity of this method is :math:`\mathcal{O}(N^2D^2)` with :math:`N` the number of pixels
    and :math:`D` the number of bands. If the image pixel values are quantized on :math:`K<<N` different values
    (eg. with a color image in :math:`[0..255]^D`), then the worst case runtime complexity can be tightened to
    :math:`\mathcal{O}(NKD^2)`.

    :See:

    This function relies on :func:`~higra.tree_fusion_depth_map` to compute the fusion of the marinal trees.

    :param image: input *color* 2d image
    :param padding: possible values are `'none'`, `'zero'`, and `'mean'` (default = `'mean'`)
    :param original_size: remove all nodes corresponding to interpolated/padded pixels (default = `True`)
    :param immersion: performs a plain map continuous immersion fo the original image (default = `True`)
    :return: a tree (Concept :class:`~higra.CptHierarchy`)
    """
    assert len(
        image.shape
    ) == 3, "This multivariate tree of shapes implementation only supports multichannel 2d images."

    ndim = image.shape[2]

    trees = tuple(
        hg.component_tree_tree_of_shapes_image2d(
            image[:, :,
                  k], padding, original_size=False, immersion=immersion)[0]
        for k in range(ndim))
    g = hg.CptHierarchy.get_leaf_graph(trees[0])
    shape = hg.CptGridGraph.get_shape(g)

    depth_map = hg.tree_fusion_depth_map(trees)

    tree, altitudes = hg.component_tree_tree_of_shapes_image2d(
        np.reshape(depth_map, shape),
        padding="none",
        original_size=False,
        immersion=False)

    if original_size and (immersion or padding != "none"):
        deleted_vertices = np.ones((tree.num_leaves(), ), dtype=np.bool)
        deleted = np.reshape(deleted_vertices, shape)

        if immersion:
            if padding != "none":
                deleted[2:-2:2, 2:-2:2] = False
            else:
                deleted[0::2, 0::2] = False
        else:
            if padding != "none":
                deleted[1:-1, 1::-1] = False

        all_deleted = hg.accumulate_sequential(tree, deleted_vertices,
                                               hg.Accumulators.min)
        shape = (image.shape[0], image.shape[1])
    else:
        all_deleted = np.zeros((tree.num_vertices(), ), dtype=np.bool)

    holes = altitudes < altitudes[tree.parents()]

    all_deleted = np.logical_or(all_deleted, holes)

    tree, _ = hg.simplify_tree(tree, all_deleted, process_leaves=True)

    g = hg.get_4_adjacency_graph(shape)
    hg.CptHierarchy.link(tree, g)

    return tree