Esempio n. 1
0
    def test_bpt_canonical_vectorial(self):
        graph = hg.get_4_adjacency_graph((2, 3))
        edge_weights = np.asarray(
            ((1, 0, 2, 1, 1, 1, 2), (0, 2, 0, 0, 1, 1, 0), (0, 5, 5, 1, 0, 1,
                                                            1))).T
        sorted_edge_indices = np.asarray((1, 0, 3, 4, 5, 6, 2))
        ref_parents = np.asarray((6, 7, 9, 6, 8, 9, 7, 8, 10, 10, 10))
        ref_mst_edge_map = np.asarray((1, 0, 3, 4, 6))
        ref_altitudes = np.asarray(((0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
                                     2), (0, 0, 0, 0, 0, 0, 2, 0, 0, 1, 0),
                                    (0, 0, 0, 0, 0, 0, 5, 0, 1, 0, 1))).T
        ref_altitudes_no_weights = np.asarray(
            (0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 5))

        tree, altitudes = hg.bpt_canonical(graph, edge_weights)
        self.assertTrue(np.all(tree.parents() == ref_parents))
        self.assertTrue(np.all(altitudes == ref_altitudes))
        self.assertTrue(np.all(tree.mst_edge_map == ref_mst_edge_map))

        tree, altitudes = hg.bpt_canonical(graph, edge_weights,
                                           sorted_edge_indices)
        self.assertTrue(np.all(tree.parents() == ref_parents))
        self.assertTrue(np.all(altitudes == ref_altitudes))
        self.assertTrue(np.all(tree.mst_edge_map == ref_mst_edge_map))

        tree, altitudes = hg.bpt_canonical(
            graph, sorted_edge_indices=sorted_edge_indices)
        self.assertTrue(np.all(tree.parents() == ref_parents))
        self.assertTrue(np.all(altitudes == ref_altitudes_no_weights))
        self.assertTrue(np.all(tree.mst_edge_map == ref_mst_edge_map))
Esempio n. 2
0
File: tree.py Progetto: higra/Higra
def filter_non_relevant_node_from_tree(tree,
                                       altitudes,
                                       non_relevant_functor,
                                       leaf_graph,
                                       canonize_tree=True):
    """
    Filter the given tree according to a functor telling if nodes are relevant or not.

    In a binary a tree, each inner node (non leaf node) is associated to the frontier separating its two children.
    If a the frontier associated to a node is considered as non relevant (for example because on of the two children
    of the node is too small) then the corresponding frontier is removed effectively merging its two children.

    This function returns a binary partition tree such that:

        - the frontiers associated to nodes marked *non-relevant* do not exist anymore;
        - the regions of the new tree are either regions of the initial tree or regions obtained by merging adjacent
          regions of the initial tree.

    If :attr:`tree` does not satisfy the concept :class:`~higra.CptBinaryHierarchy`, the given tree is first transformed
    into a binary tree (arbitrary choices are made).

    :attr:`non_relevant_functor` must be a function that accepts two arguments, a binary tree and its node altitudes,
    and must return a boolean node attribute for the given tree (ie a 1d array of boolean-ish values of size
    ``tree.num_vertices()``. A value of ``True`` is interpreted as *this node is not relevant and its associated
    frontier must be removed*.

    :See:

        :func:`~higra.filter_small_nodes_from_tree`
        :func:`~higra.filter_weak_frontier_nodes_from_tree`

    :param tree: input tree (Concept :class:`~higra.CptHierarchy`)
    :param altitudes: node altitudes of the input tree
    :param non_relevant_functor: a function that computes an attribute on a binary tree
    :param leaf_graph: graph of the tree leaves (deduced from :class:`~higra.CptHierarchy`)
    :param canonize_tree: if ``True`` (default), the resulting hierarchy is canonized (see function :func:`~higra.canonize_hierarchy`),
           otherwise the returned hierarchy is a binary tree
    :return: a tree (Concept :class:`~higra.CptHierarchy` is ``True`` and :class:`~higra.CptBinaryHierarchy` otherwise)
             and its node altitudes
    """

    if not hg.CptBinaryHierarchy.validate(tree):
        saliency = hg.saliency(tree, altitudes, leaf_graph, handle_rag=False)
        tree, altitudes = hg.bpt_canonical(leaf_graph, saliency)

    mst = hg.CptBinaryHierarchy.get_mst(tree)
    deleted_frontier_nodes = non_relevant_functor(tree, altitudes)

    mst_edge_weights = altitudes[tree.num_leaves():]
    mst_edge_weights[deleted_frontier_nodes[tree.num_leaves():]] = 0
    tree, altitudes = hg.bpt_canonical(mst, mst_edge_weights)

    if canonize_tree:
        tree, altitudes = hg.canonize_hierarchy(tree, altitudes)

    return tree, altitudes
Esempio n. 3
0
    def test_saliency(self):
        graph = hg.get_4_adjacency_graph((2, 3))
        edge_weights = np.asarray((1, 0, 2, 1, 1, 1, 2))
        sm = hg.saliency(*hg.bpt_canonical(graph, edge_weights))

        self.assertTrue(np.all(sm == edge_weights))

        labels = np.asarray(((1, 2, 3), (1, 4, 5)))
        rag = hg.make_region_adjacency_graph_from_labelisation(graph, labels)
        rag_edge_weights = np.asarray((1, 2, 1, 1, 1, 2))
        sm = hg.saliency(*hg.bpt_canonical(rag, rag_edge_weights))
        self.assertTrue(np.all(sm == edge_weights))
Esempio n. 4
0
    def test_filter_non_relevant_node_from_tree(self):
        g = hg.get_4_adjacency_graph((1, 8))
        edge_weights = np.asarray((0, 2, 0, 0, 1, 0, 0))
        tree, altitudes = hg.bpt_canonical(g, edge_weights)

        def functor(tree, altitudes):
            area = hg.attribute_area(tree)
            area_min_children = hg.accumulate_parallel(tree, area, hg.Accumulators.min)
            return area_min_children < 3

        res_tree, res_altitudes = hg.filter_non_relevant_node_from_tree(tree, altitudes, functor, canonize_tree=False)

        sm = hg.saliency(res_tree, res_altitudes)
        sm_ref = np.asarray((0, 0, 0, 0, 1, 0, 0))
        self.assertTrue(np.all(sm == sm_ref))

        ref_parents = (8, 8, 9, 9, 10, 11, 11, 12, 13, 10, 13, 12, 14, 14, 14)
        ref_altitudes = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
        self.assertTrue(hg.CptBinaryHierarchy.validate(res_tree))
        self.assertTrue(np.all(ref_parents == res_tree.parents()))
        self.assertTrue(np.all(res_altitudes == ref_altitudes))

        res_tree, res_altitudes = hg.filter_non_relevant_node_from_tree(tree, altitudes, functor, canonize_tree=True)

        sm = hg.saliency(res_tree, res_altitudes)
        sm_ref = np.asarray((0, 0, 0, 0, 1, 0, 0))
        self.assertTrue(np.all(sm == sm_ref))

        ref_parents = (8, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10)
        ref_tree = hg.Tree(ref_parents)
        ref_altitudes = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)
        self.assertFalse(hg.CptBinaryHierarchy.validate(res_tree))
        self.assertTrue(hg.test_tree_isomorphism(res_tree, ref_tree))
        self.assertTrue(np.all(res_altitudes == ref_altitudes))
Esempio n. 5
0
    def test_BPT(self):
        graph = hg.get_4_adjacency_graph((2, 3))

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

        tree, altitudes = hg.bpt_canonical(graph, edge_weights)
        mst = hg.CptBinaryHierarchy.construct(tree)["mst"]
        mst_edge_map = hg.get_attribute(mst, "mst_edge_map")

        self.assertTrue(tree.num_vertices() == 11)
        self.assertTrue(tree.num_edges() == 10)
        self.assertTrue(
            np.allclose(tree.parents(), (6, 7, 9, 6, 8, 9, 7, 8, 10, 10, 10)))
        self.assertTrue(
            np.allclose(altitudes, (0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2)))
        self.assertTrue(mst.num_vertices() == 6)
        self.assertTrue(mst.num_edges() == 5)

        ref = [(0, 3), (0, 1), (1, 4), (2, 5), (1, 2)]
        test = []
        for e in mst.edges():
            test.append((e[0], e[1]))
        self.assertTrue(ref == test)

        self.assertTrue(np.all(mst_edge_map == (1, 0, 3, 4, 2)))
def subdominant_ultrametric(graph, edge_weights, return_hierarchy=False, dtype=tc.float64):
    """ 
    Subdominant (single linkage) ultrametric of an edge weighted graph.
    
    :param graph: input graph (class ``higra.UndirectedGraph``)
    :param edge_weights: edge weights of the input graph (pytorch tensor, autograd is supported)
    :param return_hierarchy: if ``True``,  the dendrogram representing the hierarchy is also returned as a tuple ``(tree, altitudes)``
    :return: the subdominant ultrametric of the input edge weighted graph (pytorch tensor) (and the hierarchy if ``return_hierarchy`` is ``True``)
    """  
    # compute single linkage if not already provided
    
    tree, altitudes_ = hg.bpt_canonical(graph, edge_weights.detach().numpy())

    # lowest common ancestors of every edge of the graph
    lca_map = hg.attribute_lca_map(tree)
    # the following is used to map lca node indices to their corresponding edge indices in the input graph
    # associated minimum spanning
    mst = hg.get_attribute(tree, "mst")
    # map mst edges to graph edges
    mst_map = hg.get_attribute(mst, "mst_edge_map")
    # bijection between single linkage node and mst edges
    mst_idx = lca_map - tree.num_leaves()
    # mst edge indices in the input graph
    edge_idx = mst_map[mst_idx]

    altitudes = edge_weights[mst_map]
    # sanity check
    # assert(np.all(altitudes.detach().numpy() == altitudes_[tree.num_leaves():]))
    ultrametric = edge_weights[edge_idx]
    
    if return_hierarchy:
        return ultrametric, (tree, tc.cat((tc.zeros(tree.num_leaves(), dtype=dtype), altitudes)))
    else:
        return ultrametric
Esempio n. 7
0
    def test_dasgupta_cost(self):
        g = hg.get_4_adjacency_graph((3, 3))
        edge_weights = np.asarray((1, 7, 3, 7, 1, 1, 6, 5, 6, 4, 1, 2))
        tree, _ = hg.bpt_canonical(g, edge_weights)

        cost = hg.dasgupta_cost(tree, edge_weights)

        ref_cost = 2 / 1 + 4 / 3 + 9 / 7 + 9 / 7 + 2 / 1 + 2 / 1 + 9 / 5 + 9 / 6 + 9 / 6 + 7 / 4 + 2 / 1 + 3 / 2
        self.assertTrue(np.isclose(cost, ref_cost))
Esempio n. 8
0
    def test_filter_weak_frontier_nodes_from_tree(self):
        g = hg.get_4_adjacency_graph((1, 8))
        edge_weights = np.asarray((0, 2, 0, 0, 1, 0, 0))
        tree, altitudes = hg.bpt_canonical(g, edge_weights)

        res_tree, res_altitudes = hg.filter_weak_frontier_nodes_from_tree(tree, altitudes, edge_weights, 2)

        sm = hg.saliency(res_tree, res_altitudes)
        sm_ref = np.asarray((0, 2, 0, 0, 0, 0, 0))
        self.assertTrue(np.all(sm == sm_ref))
Esempio n. 9
0
    def test_bpt_canonical_options(self):
        graph = hg.get_4_adjacency_graph((2, 3))
        edge_weights = np.asarray((1, 0, 2, 1, 1, 1, 2))
        sorted_edge_indices = np.asarray((1, 0, 3, 4, 5, 2, 6))
        ref_parents = np.asarray((6, 7, 9, 6, 8, 9, 7, 8, 10, 10, 10))
        ref_mst_edge_map = np.asarray((1, 0, 3, 4, 2))
        ref_altitudes = np.asarray((0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2))
        ref_altitudes_no_weights = np.asarray(
            (0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 5))

        tree = hg.bpt_canonical(graph,
                                edge_weights,
                                return_altitudes=False,
                                compute_mst=False)
        self.assertTrue(np.all(tree.parents() == ref_parents))
        self.assertTrue(hg.get_attribute(tree, "mst") is None)
        self.assertTrue(np.all(tree.mst_edge_map == ref_mst_edge_map))
        mst = hg.CptBinaryHierarchy.get_mst(tree)
        self.assertTrue(mst.num_vertices() == 6)
        self.assertTrue(mst.num_edges() == 5)
        self.assertTrue(np.all(mst.mst_edge_map == ref_mst_edge_map))

        tree, altitudes = hg.bpt_canonical(graph,
                                           edge_weights,
                                           return_altitudes=True,
                                           compute_mst=True)
        self.assertTrue(np.all(tree.parents() == ref_parents))
        self.assertTrue(tree.mst is not None)
        self.assertTrue(tree.mst.num_vertices() == 6)
        self.assertTrue(tree.mst.num_edges() == 5)
        self.assertTrue(np.all(tree.mst.mst_edge_map == ref_mst_edge_map))
        self.assertTrue(np.all(tree.mst_edge_map == ref_mst_edge_map))
        self.assertTrue(np.all(altitudes == ref_altitudes))

        tree, altitudes = hg.bpt_canonical(graph, edge_weights,
                                           sorted_edge_indices)
        self.assertTrue(np.all(tree.parents() == ref_parents))
        self.assertTrue(np.all(altitudes == ref_altitudes))

        tree, altitudes = hg.bpt_canonical(
            graph, sorted_edge_indices=sorted_edge_indices)
        self.assertTrue(np.all(tree.parents() == ref_parents))
        self.assertTrue(np.all(altitudes == ref_altitudes_no_weights))
Esempio n. 10
0
    def test_frontier_length_rag(self):
        g = hg.get_4_adjacency_graph((3, 3))
        vertex_labels = np.asarray(((0, 1, 1),
                                    (0, 2, 2),
                                    (3, 2, 4)))
        rag = hg.make_region_adjacency_graph_from_labelisation(g, vertex_labels)
        edge_weights = np.asarray((1, 5, 4, 3, 6, 2))
        tree, altitudes = hg.bpt_canonical(rag, edge_weights)

        ref_attribute = [0, 0, 0, 0, 0, 1, 2, 1, 4]
        attribute = hg.attribute_frontier_length(tree)
        self.assertTrue(np.allclose(ref_attribute, attribute))
Esempio n. 11
0
    def test_contour_length_rag_partition_tree(self):
        g = hg.get_4_adjacency_graph((3, 3))
        vertex_labels = np.asarray(((0, 1, 1), (0, 2, 2), (3, 2, 4)))
        rag = hg.make_region_adjacency_graph_from_labelisation(
            g, vertex_labels)
        edge_weights = np.asarray((1, 5, 4, 3, 6, 2), dtype=np.float64)
        tree, altitudes = hg.bpt_canonical(rag, edge_weights)

        ref_attribute = np.asarray([3, 3, 6, 2, 2, 4, 4, 4, 0],
                                   dtype=np.float64)
        attribute = hg.attribute_contour_length(tree)
        self.assertTrue(np.allclose(ref_attribute, attribute))
Esempio n. 12
0
    def test_area_default_param(self):
        g = hg.get_4_adjacency_graph((2, 3))
        edge_weights = np.asarray((1, 4, 6, 5, 2, 7, 3))

        ref_area = (1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 6)

        tree, altitudes = hg.bpt_canonical(g, edge_weights)
        area = hg.attribute_area(tree)
        self.assertTrue(np.all(ref_area == area))

        tree2 = hg.Tree(tree.parents())
        area2 = hg.attribute_area(tree2)
        self.assertTrue(np.all(ref_area == area2))
Esempio n. 13
0
    def test_BPTTrivial(self):
        graph = hg.get_4_adjacency_graph((1, 3))

        edge_weights = np.asarray([2, 3])

        tree, altitudes = hg.bpt_canonical(graph, edge_weights)
        mst = hg.CptBinaryHierarchy.construct(tree)["mst"]

        self.assertTrue(tree.num_vertices() == 5)
        self.assertTrue(tree.num_edges() == 4)
        self.assertTrue(np.allclose(tree.parents(), (3, 3, 4, 4, 4)))
        self.assertTrue(np.allclose(altitudes, (0, 0, 0, 2, 3)))
        self.assertTrue(mst.num_vertices() == 3)
        self.assertTrue(mst.num_edges() == 2)
Esempio n. 14
0
    def test_frontier_strength_rag(self):
        g = hg.get_4_adjacency_graph((3, 3))
        vertex_labels = np.asarray(((0, 1, 1),
                                    (0, 2, 2),
                                    (3, 2, 4)))
        rag = hg.make_region_adjacency_graph_from_labelisation(g, vertex_labels)
        rag_edge_weights = np.asarray((1, 5, 4, 3, 6, 2))
        tree, altitudes = hg.bpt_canonical(rag, rag_edge_weights)
        # tree is [5 5 6 7 6 7 8 8 8]
        edge_weights = np.asarray((1, 6, 2, 6, 1, 1, 5, 4, 5, 3, 1, 1), dtype=np.float64)

        ref_attribute = [0, 0, 0, 0, 0, 1, 2, 5, 9 / 4]
        attribute = hg.attribute_frontier_strength(tree, edge_weights)
        self.assertTrue(np.allclose(ref_attribute, attribute))
Esempio n. 15
0
    def test_filter_non_relevant_node_from_tree(self):
        g = hg.get_4_adjacency_graph((1, 8))
        edge_weights = np.asarray((0, 2, 0, 0, 1, 0, 0))
        tree, altitudes = hg.bpt_canonical(g, edge_weights)

        def functor(tree, altitudes):
            area = hg.attribute_area(tree)
            area_min_children = hg.accumulate_parallel(tree, area, hg.Accumulators.min)
            return area_min_children < 3

        res_tree, res_altitudes = hg.filter_non_relevant_node_from_tree(tree, altitudes, functor)

        sm = hg.saliency(res_tree, res_altitudes)
        sm_ref = np.asarray((0, 0, 0, 0, 1, 0, 0))
        self.assertTrue(np.all(sm == sm_ref))
Esempio n. 16
0
    def test_BPT_from_edge_list(self):
        graph = hg.get_4_adjacency_graph((2, 3))
        sources, targets = graph.edge_list()
        edge_weights = np.asarray((1, 0, 2, 1, 1, 1, 2))

        tree, altitudes = hg.bpt_canonical(
            (sources, targets, graph.num_vertices()), edge_weights)
        mst_edge_map = tree.mst_edge_map

        self.assertTrue(tree.num_vertices() == 11)
        self.assertTrue(tree.num_edges() == 10)
        self.assertTrue(
            np.allclose(tree.parents(), (6, 7, 9, 6, 8, 9, 7, 8, 10, 10, 10)))
        self.assertTrue(
            np.allclose(altitudes, (0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2)))

        self.assertTrue(np.all(mst_edge_map == (1, 0, 3, 4, 2)))
Esempio n. 17
0
    def get_test_tree():
        """
        base graph is

        (0)-- 0 --(1)-- 2 --(2)
         |         |         |
         6         6         0
         |         |         |
        (3)-- 0 --(4)-- 4 --(5)
         |         |         |
         5         5         3
         |         |         |
        (6)-- 0 --(7)-- 1 --(8)

        Minima are
        A: (0,1)
        B: (3,4)
        C: (2,5)
        D: (6,7)

        BPT:




        4                 +-------16------+
                          |               |
        3         +-------15-----+        |
                  |              |        |
        2     +---14--+          |        |
              |       |          |        |
        1     |       |       +--13-+     |
              |       |       |     |     |
        0   +-9-+   +-10+   +-12+   |   +-11+
            +   +   +   +   +   +   +   +   +
            0   1   2   5   6   7   8   3   4


        :return:
        """

        g = hg.get_4_adjacency_graph((3, 3))
        edge_weights = np.asarray((0, 6, 2, 6, 0, 0, 5, 4, 5, 3, 0, 1))

        return hg.bpt_canonical(g, edge_weights)
Esempio n. 18
0
def ultrametric_open(graph, edge_weights):
    """
    Subdominant ultrametric of the given edge weighted graph.

    The subdominant ultrametric relative to a given dissimilarity measure (here the graph edge weights)
    is defined as the largest ultrametric smaller than the dissimilarity measure.

    In the case of an edge weighted undirected graph, the value of the subdominant ultrametric on the
    edge :math:`e_{xy}` is given by the min-max distance between :math:`x` and :math:`y`.

    Complexity: :math:`\mathcal{O}(n*log(n))` with :math:`n` the number of edges in the graph

    :param graph: Input graph
    :param edge_weights: Graph edge weights
    :return: edge weights corresponding to the subdominant ultrametric
    """
    tree, altitudes = hg.bpt_canonical(edge_weights, graph)
    return hg.saliency(altitudes)
Esempio n. 19
0
def binary_partition_tree_single_linkage(graph, edge_weights):
    """
    Alias for :func:`~higra.bpt_canonical`.

    Given a graph :math:`G=(V, E)`, with initial edge weights :math:`w`,
    the distance :math:`d(X,Y)` between any two clusters :math:`X` and :math:`Y` is

    .. math::

        d(X,Y) = \min \{w(\{x,y\}) | x \in X, y \in Y, \{x,y\} \in E \}

    Regions are then iteratively merged following the above distance (closest first) until a single region remains.

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

    return hg.bpt_canonical(graph, edge_weights)
Esempio n. 20
0
    def test_contour_strength_rag_partition_tree(self):
        g = hg.get_4_adjacency_graph((3, 3))
        vertex_labels = np.asarray(((0, 1, 1), (0, 2, 2), (3, 2, 4)))

        base_edge_weights = np.arange(g.num_edges(), dtype=np.float64)

        rag = hg.make_region_adjacency_graph_from_labelisation(
            g, vertex_labels)
        edge_weights = np.asarray((1, 5, 4, 3, 6, 2))
        tree, altitudes = hg.bpt_canonical(rag, edge_weights)

        attribute = hg.attribute_contour_strength(tree, base_edge_weights)

        ref_perimeter = np.asarray([3, 3, 6, 2, 2, 4, 4, 4, 1],
                                   dtype=np.float64)
        ref_weights = np.asarray([11, 7, 42, 16, 20, 18, 22, 22, 0],
                                 dtype=np.float64)

        self.assertTrue(np.allclose(ref_weights / ref_perimeter, attribute))
Esempio n. 21
0
    def forward(ctx,
                xedge_weights,
                graph,
                hierarchy=None,
                lambda_=1,
                top_nodes=0):
        edge_weights = xedge_weights.detach().numpy()

        if hierarchy is None:
            tree, altitudes = higra.bpt_canonical(graph, edge_weights)
        else:
            tree, altitudes = hierarchy

        if type(altitudes) is torch.Tensor:
            altitudes = altitudes.contiguous().detach().numpy()

        mst = higra.CptBinaryHierarchy.get_mst(tree)
        # for each edge index ei of the mst, mst_edge_map[ei] gives the index of the edge in the base graph
        mst_edge_map = higra.CptMinimumSpanningTree.get_edge_map(mst)
        op = cpp_softarea.forward(tree, altitudes, mst, mst_edge_map, lambda_,
                                  top_nodes)
        ctx.intermediate_results = (graph, tree, altitudes, mst, mst_edge_map,
                                    lambda_, *op[1:])
        return torch.as_tensor(op[0], dtype=xedge_weights.dtype)
Esempio n. 22
0
def align_hierarchies(graph, vertex_labels, other_hierarchies):
    """
    Align hierarchies boundaries on the boundaries of the provided super-vertex decomposition of a graph

    Given:

        - a graph :math:`G=(V,E)`
        - a fine labelisation :math:`l_1` of the vertices of :math:`G`;
        - a tree :math:`T` on :math:`G` whose supervertices corresponds to the coarse labelisation :math:`l_2`
          of the vertices of :math:`G`; and
        - the altitudes :math:`a` of the nodes of :math:`T`.

    Let us denote:

        - given a vertex :math:`x` of :math:`G` and a labelisation :math:`l`, :math:`l(x)` is the region of :math:`l`
          that contains :math:`x`
        - given a region :math:`r` of :math:`l_1`, :math:`s(r, l_2)` is the region :math:`R` of :math:`l_2` that has
          the largest intersection with :math:`r`, ie, :math:`s(r, l_2) = \\arg \max_{R \in l_2} | R \cap r |`

    The projection of :math:`T` onto :math:`l_1` is a hierarchy given by the saliency map :math:`sm` on :math:`G`
    defined by:

    .. math::

        \\forall e_{xy} \in E, sm(e_{xy}) = a(lca_T(s(l_1(x), l_2), s(l_1(y), l_2)))

    where :math:`lca_T(x, y)` is the lowest common ancestor of nodes :math:`x` and :math:`y` in :math:`T`.

    :param graph: the domain graph
    :param vertex_labels: 1d array of positive integers, labeling of the graph vertices into super-vertices
    :param other_hierarchies: a hierarchy or a list of hierarchies: hierarchies can be given either as valued trees
        (pairs (tree, altitudes) ) or as saliency maps (pairs (graph, edge_weights)), defined on the pixel graph or
        on a region adjacency graph (Concept :class:`~higra.CptRegionAdjacencyGraph`).
    :return: a hierarchy or a list of hierarchies as saliency maps
    """
    result = []
    list_input = True
    if not hg.is_iterable(other_hierarchies):
        raise TypeError("bas format for other hierarchies.")

    first_element = other_hierarchies[0]
    if not hg.is_iterable(first_element):
        list_input = False
        other_hierarchies = (other_hierarchies,)

    aligner = hg.HierarchyAligner.from_labelisation(graph, vertex_labels)

    for hierarchy in other_hierarchies:
        obj, values = hierarchy
        if type(obj) is hg.Tree:
            leaf_graph = hg.CptHierarchy.get_leaf_graph(obj)
            if leaf_graph is not None and hg.CptRegionAdjacencyGraph.validate(leaf_graph):
                vertex_map = hg.CptRegionAdjacencyGraph.get_vertex_map(leaf_graph)
                r = aligner.align_hierarchy(vertex_map, obj, values)
            else:
                r = aligner.align_hierarchy(obj, values)

        elif type(obj) is hg.UndirectedGraph:
            if hg.CptRegionAdjacencyGraph.validate(obj):
                vertex_map = hg.CptRegionAdjacencyGraph.get_vertex_map(obj)
                bpt, altitudes = hg.bpt_canonical(obj, values)
                r = aligner.align_hierarchy(vertex_map, bpt, altitudes)
            else:
                r = aligner.align_hierarchy(obj, values)

        else:
            raise Exception("Hierarchy format not recognized: " + str(hierarchy))
        result.append(r)
    if not list_input:
        return result[0]
    return result