Example #1
0
File: tree.py Project: higra/Higra
def binary_labelisation_from_markers(tree,
                                     object_marker,
                                     background_marker,
                                     leaf_graph=None):
    """
    Given two binary markers :math:`o` (object) and :math:`b` (background) (given by their indicator functions)
    on the leaves of a tree :math:`T`, the corresponding binary labelization of the leaves of :math:`T` is defined as
    the union of all the nodes intersecting :math:`o` but not :math:`b`:

    .. math::

        res = \\bigcup \{R \in T \mid R \cap o \\neq \emptyset, \\textrm{ and } R \cap b = \emptyset\}

    :param tree: input tree (Concept :class:`~higra.CptHierarchy`)
    :param object_marker: indicator function of the object marker: 1d array of size tree.num_leaves() where non zero values correspond to the object marker
    :param background_marker: indicator function of the background marker: 1d array of size tree.num_leaves() where non zero values correspond to the background marker
    :param leaf_graph: graph on the leaves of the input tree (optional, deduced from :class:`~higra.CptHierarchy`)
    :return: Leaf labels
    """

    if leaf_graph is not None:
        object_marker = hg.linearize_vertex_weights(object_marker, leaf_graph)
        background_marker = hg.linearize_vertex_weights(
            background_marker, leaf_graph)

    object_marker, background_marker = hg.cast_to_common_type(
        object_marker, background_marker)
    labels = hg.cpp._binary_labelisation_from_markers(tree, object_marker,
                                                      background_marker)

    if leaf_graph is not None:
        labels = hg.delinearize_vertex_weights(labels, leaf_graph)

    return labels
Example #2
0
def binary_partition_tree_average_linkage(graph,
                                          edge_weights,
                                          edge_weight_weights=None):
    """
    Binary partition tree with average linkage distance.

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

    .. math::

        d(X,Y) = \\frac{1}{Z} \sum_{x \in X, y \in Y, \{x,y\} \in E} w(\{x,y\}) \\times w_2(\{x,y\})

    with :math:`Z = \sum_{x \in X, y \in Y, \{x,y\} \in E} w_2({x,y})`.

    :param graph: input graph
    :param edge_weights: edge weights of the input graph
    :param edge_weight_weights: weighting of edge weights of the input graph (default to an array of ones)
    :return: a tree (Concept :class:`~higra.CptHierarchy`) and its node altitudes
    """

    if edge_weight_weights is None:
        edge_weight_weights = np.ones_like(edge_weights)
    else:
        edge_weights, edge_weight_weights = hg.cast_to_common_type(
            edge_weights, edge_weight_weights)

    tree, altitudes = hg.cpp._binary_partition_tree_average_linkage(
        graph, edge_weights, edge_weight_weights)

    hg.CptHierarchy.link(tree, graph)

    return tree, altitudes
Example #3
0
def mean_pb_hierarchy(graph, edge_weights, shape, edge_orientations=None):
    """
    Mean probability boundary hierarchy.

    The method is described in:

        P. Arbelaez, M. Maire, C. Fowlkes and J. Malik, "Contour Detection and Hierarchical Image Segmentation,"
        in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 33, no. 5, pp. 898-916, May 2011.
        doi: 10.1109/TPAMI.2010.161

    This does not include gradient estimation.

    The returned hierarchy is defined on the gradient watershed super-pixels.

    The final sigmoid scaling of the hierarchy altitude is not performed.

    :param graph: must be a 4 adjacency graph (Concept :class:`~higra.CptGridGraph`)
    :param edge_weights: gradient value on edges
    :param shape: shape of the graph, i.e. a pair (height, width) (deduced from :class:`~higra.CptGridGraph`)
    :param edge_orientations: estimated orientation of the gradient on edges (optional)
    :return: a tree (Concept :class:`~higra.CptHierarchy`) and its node altitudes
    """

    shape = hg.normalize_shape(shape)
    if edge_orientations is not None:
        edge_weights, edge_orientations = hg.cast_to_common_type(
            edge_weights, edge_orientations)
    rag, vertex_map, edge_map, tree, altitudes = hg.cpp._mean_pb_hierarchy(
        graph, shape, edge_weights, edge_orientations)

    hg.CptRegionAdjacencyGraph.link(rag, graph, vertex_map, edge_map)
    hg.CptHierarchy.link(tree, rag)

    return tree, altitudes
Example #4
0
def oriented_watershed(graph, edge_weights, shape, edge_orientations=None):
    """
    Creates a region adjacency graph (rag) with the oriented watershed transform.

    The method is described in:

        P. Arbelaez, M. Maire, C. Fowlkes and J. Malik, "Contour Detection and Hierarchical Image Segmentation,"
        in IEEE Transactions on Pattern Analysis and Machine Intelligence, vol. 33, no. 5, pp. 898-916, May 2011.
        doi: 10.1109/TPAMI.2010.161

    If no edge orientations are provided, then the weight of a rag edge between region i and j is the mean weight of the
    edges linking a vertex of i to a vertex of j.

    This does not include gradient estimation.

    :param graph: must be a 4 adjacency graph (Concept :class:`~higra.CptGridGraph`)
    :param edge_weights: gradient value on edges
    :param shape: shape of the graph, i.e. a pair (height, width) (deduced from :class:`~higra.CptGridGraph`)
    :param edge_orientations: estimated orientation of the gradient on edges (optional)
    :return: a pair (rag, rag_edge_weights): the region adjacency graph (Concept :class:`~higra.CptRegionAdjacencyGraph`) and its estimated edge_weights
    """

    shape = hg.normalize_shape(shape)
    if edge_orientations is not None:
        edge_weights, edge_orientations = hg.cast_to_common_type(
            edge_weights, edge_orientations)
    rag, vertex_map, edge_map, rag_edge_weights = hg.cpp._oriented_watershed(
        graph, shape, edge_weights, edge_orientations)

    hg.CptRegionAdjacencyGraph.link(rag, graph, vertex_map, edge_map)

    return rag, rag_edge_weights
Example #5
0
def project_fine_to_coarse_labelisation(labelisation_fine, labelisation_coarse, num_regions_fine=0, num_regions_coarse=0):
    """
    Find for each label (ie region) of the fine labelisation, the label of the region in the
    coarse labelisation that maximises the intersection with the fine region.

    Pre-condition:

        - :math:`range(labelisation\_fine) = [0, \ldots, num\_regions\_fine[`
        - :math:`range(labelisation\_coarse) = [0, \ldots, num\_regions\_coarse[`

    Then, for all label :math:`i \in [0, \ldots, num\_regions\_fine[`,

    .. math::

        result(i) = \\arg \max_{j \in [0, \ldots, num\_regions\_coarse[} | (fine\_labelisation == i) \cap (coarse\_labelisation == j) |


    If :attr:`num_regions_fine` or :attr:`num_regions_coarse` are not provided, they will be determined as
    :math:`max(labelisation\_fine) + 1` and  :math:`max(labelisation\_coarse) + 1`

    :param labelisation_fine: 1d array of positive integers
    :param labelisation_coarse: 1d array of positive integers (same length as :attr:`labelisation_fine`)
    :param num_regions_fine: optional, number of different labels in :attr:`labelisation_fine`
    :param num_regions_coarse: optional, number of different labels in :attr:`labelisation_coarse`
    :return: a 1d array mapping fine labels to coarse labels
    """
    labelisation_fine, labelisation_coarse = hg.cast_to_common_type(labelisation_fine, labelisation_coarse)

    return hg.cpp._project_fine_to_coarse_labelisation(labelisation_fine, labelisation_coarse, num_regions_fine, num_regions_coarse)
Example #6
0
def accumulate_and_max_sequential(tree,
                                  node_weights,
                                  leaf_data,
                                  accumulator,
                                  leaf_graph=None):
    """
    Accumulates node values from the leaves to the root and takes the maximum of result and the input array.


    For each leaf node :math:`i`, :math:`output(i) = leaf_data(i)`.
    For each node :math:`i` from the leaves (excluded) to the root, :math:`output(i) = \max(input(i), accumulator(output(children(i)))`


    :param tree: input tree (Concept :class:`~higra.CptHierarchy`)
    :param node_weights: Weights on the nodes of the tree
    :param leaf_data: Weights on the leaves of the tree
    :param accumulator: see :class:`~higra.Accumulators`
    :param leaf_graph: graph of the tree leaves (optional, deduced from :class:`~higra.CptHierarchy`)
    :return: returns new tree node weights
    """
    if leaf_graph is not None:
        leaf_data = hg.linearize_vertex_weights(leaf_data, leaf_graph)

    leaf_data, node_weights = hg.cast_to_common_type(leaf_data, node_weights)
    res = hg.cpp._accumulate_and_max_sequential(tree, node_weights, leaf_data,
                                                accumulator)
    return res
Example #7
0
def binary_partition_tree_exponential_linkage(graph,
                                              edge_weights,
                                              alpha,
                                              edge_weight_weights=None):
    """
    Binary partition tree with exponential linkage distance.

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

    .. math::

         d(X,Y) = \\frac{1}{Z} \sum_{x \in X, y \in Y, \{x,y\} in E} w_2(\{x,y\}) \\times \exp(\\alpha * w(\{x,y\})) \\times w(\{x,y\})

    with :math:`Z = \sum_{x \in X, y \in Y, \{x,y\} \in E} w_2(\{x,y\}) \\times \exp(\\alpha * w(\{x,y\}))`.

    :See:

         Nishant Yadav, Ari Kobren, Nicholas Monath, Andrew Mccallum.
         `Supervised Hierarchical Clustering with Exponential Linkage <http://proceedings.mlr.press/v97/yadav19a.html>`_
         Proceedings of the 36th International Conference on Machine Learning, PMLR 97:6973-6983, 2019.

    :param graph: input graph
    :param edge_weights: edge weights of the input graph
    :param alpha: exponential parameter
    :param edge_weight_weights: weighting of edge weights of the input graph (default to an array of ones)
    :return: a tree (Concept :class:`~higra.CptHierarchy`) and its node altitudes
    """

    alpha = float(alpha)

    if edge_weight_weights is None:
        edge_weight_weights = np.ones_like(edge_weights)
    else:
        edge_weights, edge_weight_weights = hg.cast_to_common_type(
            edge_weights, edge_weight_weights)

    # special cases: improve efficiency and avoid numerical issues
    if alpha == 0:
        tree, altitudes = hg.binary_partition_tree_average_linkage(
            graph, edge_weights, edge_weight_weights)
    elif alpha == float('-inf'):
        tree, altitudes = hg.binary_partition_tree_single_linkage(
            graph, edge_weights)
    elif alpha == float('inf'):
        tree, altitudes = hg.binary_partition_tree_complete_linkage(
            graph, edge_weights)
    else:
        res = hg.cpp._binary_partition_tree_exponential_linkage(
            graph, edge_weights, alpha, edge_weight_weights)
        tree = res.tree()
        altitudes = res.altitudes()

    hg.CptHierarchy.link(tree, graph)

    return tree, altitudes
Example #8
0
    def test_cast_to_common_type(self):
        a_uint16 = np.zeros((1, 1), dtype=np.uint16)
        a_int8 = np.zeros((1, 1), dtype=np.int8)
        a_uint64 = np.zeros((1, 1), dtype=np.uint64)
        a_int64 = np.zeros((1, 1), dtype=np.int64)

        a, b, c, d = hg.cast_to_common_type(a_uint16, a_int8, a_int64, a_uint64)
        self.assertTrue(a.dtype == np.int64)
        self.assertTrue(b.dtype == np.int64)
        self.assertTrue(c.dtype == np.int64)
        self.assertTrue(d.dtype == np.int64)

        self.assertTrue(id(c) == id(a_int64))
Example #9
0
def binary_partition_tree_ward_linkage(graph,
                                       vertex_centroids,
                                       vertex_sizes=None,
                                       altitude_correction="max"):
    """
    Binary partition tree with the Ward linkage rule.

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

    .. math::

        d(X,Y) = \\frac{| X |\\times| Y |}{| X |+| Y |} \| \\vec{X} - \\vec{Y} \|^2

    where :math:`\\vec{X}` and :math:`\\vec{Y}` are the centroids of  :math:`X` and  :math:`Y`.

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

    Note that the Ward distance is not necessarily strictly increasing when processing a non complete graph.
    This can be corrected afterward with an altitude correction strategy. Valid values for ``altitude correction`` are:

        - ``"none"``: nothing is done and the altitude of a node is equal to the Ward distance between its 2 children;
          this may not be non-decreasing
        - ``"max"``: the altitude of a node :math:`n` is defined as the maximum of the the Ward distance associated
          to each node in the subtree rooted in :math:`n`.

    :param graph: input graph
    :param vertex_centroids: Centroids of the graph vertices (must be a 2d array)
    :param vertex_sizes: Size (number of elements) of the graph vertices (default to an array of ones)
    :param altitude_correction: can be ``"none"`` or ``"max"`` (default)
    :return: a tree (Concept :class:`~higra.CptHierarchy`) and its node altitudes
    """

    if vertex_sizes is None:
        vertex_sizes = np.ones((graph.num_vertices(), ),
                               dtype=vertex_centroids.dtype)
    else:
        vertex_centroids, vertex_sizes = hg.cast_to_common_type(
            vertex_centroids, vertex_sizes)

    res = hg.cpp._binary_partition_tree_ward_linkage(graph, vertex_centroids,
                                                     vertex_sizes,
                                                     altitude_correction)
    tree = res.tree()
    altitudes = res.altitudes()

    hg.CptHierarchy.link(tree, graph)

    return tree, altitudes
Example #10
0
def attribute_extinction_value(tree,
                               altitudes,
                               attribute,
                               increasing_altitudes="auto"):
    """
    The extinction value of a node :math:`n` of the input tree :math:`T` with increasing altitudes :math:`alt`
    for the increasing attribute :math:`att` is the equal to the threshold :math:`k` such that the node :math:`n`
    is still in an minima of :math:`t` when all nodes having an attribute value smaller than :math:`k` are removed.

    Formally, let :math:`\{M_i\}` be the set of minima of the hierarchy :math:`T` with altitudes :math:`alt`.
    Let :math:`prec` be a total ordering of :math:`\{M_i\}` such that :math:`M_i \prec M_j \Rightarrow alt(M_i) \leq alt(M_j)`.
    Let :math:`r(M_i)` be the smallest node of :math:`t` containing :math:`M_i` and another minima :math:`M_j` such
    that :math:`M_j \prec M_i`. The extinction value of :math:`M_i` is then defined as :math:`alt(r(M_i)) - alt(M_i)`.

    Extinction values of minima are then extended to other nodes in the tree with the following rules:

        - the extinction value of a non-leaf node :math:`n` which is not a minimum is defined as the largest
          extinction values among all the minima contained in :math:`n`
          (and 0 if :math:`n` does not contain any minima); and
        - the extinction value of a leaf node :math:`n` belonging to a minima :math:`M_i` is equal to the extinction
          value of :math:`M_i`. I :math:`n` does not belong to any minima its extinction value is 0.

    The function can also handle decreasing altitudes, in which case *minima* should be replaced by *maxima*
    in the description above. Possible values of :attr:`increasing_altitude` are:

        - ``'auto'``: the function will automatically determine if :attr:`altitudes` are increasing or decreasing (this has
          small computational cost but does not impact the runtime complexity).
        - ``True`` or ``'increasing'``: this means that altitudes are increasing from the leaves to the root
          (ie. for any node :math:`n`, :math:`altitudes(n) \leq altitudes(parent(n))`.
        - ``False`` or ``'decreasing'``: this means that altitudes are decreasing from the leaves to the root
          (ie. for any node :math:`n`, :math:`altitude(n) \geq altitude(parent(n))`.


    :param tree: Input tree
    :param altitudes: Tree node altitudes
    :param attribute: Tree node attribute
    :param increasing_altitudes: possible values 'auto', True, False, 'increasing', and 'decreasing'
    :return: a 1d array like :attr:`attribute`
    """
    inc = __process_param_increasing_altitudes(tree, altitudes,
                                               increasing_altitudes)

    altitudes, attribute = hg.cast_to_common_type(altitudes, attribute)

    res = hg.cpp._attribute_extinction_value(tree, altitudes, attribute, inc)

    return res
Example #11
0
def hierarchy_to_optimal_energy_cut_hierarchy(
        tree,
        data_fidelity_attribute,
        regularization_attribute,
        approximation_piecewise_linear_function=10):
    """
    Transforms the given hierarchy into its optimal energy cut hierarchy for the given energy terms.
    In the optimal energy cut hierarchy, any horizontal cut corresponds to an optimal energy cut in the original
    hierarchy.

    Each node :math:`i` of the tree is associated to a data fidelity energy :math:`D(i)` and a regularization energy :math:`R(i)`.
    The algorithm construct a new hierarchy with associated altitudes such that the horizontal cut of level lambda
    is the optimal cut for the energy attribute :math:`D + \lambda * R` of the input tree (see function :func:`~higra.labelisation_optimal_cut_from_energy`).
    In other words, the horizontal cut of level :math:`\lambda` in the result is the cut of the input composed of the nodes :math:`N` such that
    :math:`\sum_{r \in N} D(r) + \lambda * R(r)` is minimal.

    PRECONDITION: the regularization energy :math:`R` must be sub additive: for each node :math:`i`: :math:`R(i) \leq \sum_{c\in Children(i)}R(c)`

    The algorithm runs in linear time :math:`\mathcal{O}(n)`

    See:

        Laurent Guigues, Jean Pierre Cocquerez, Hervé Le Men.
        `Scale-sets Image Analysis. International <https://hal.archives-ouvertes.fr/hal-00705364/file/ijcv_scale-setV11.pdf>`_
        Journal of Computer Vision, Springer Verlag, 2006, 68 (3), pp.289-317

    :param tree: input tree
    :param data_fidelity_attribute: 1d array representing the data fidelity energy of each node of the input tree
    :param regularization_attribute: 1d array representing the regularization energy of each node of the input tree
    :param approximation_piecewise_linear_function: Maximum number of pieces used in the approximated piecewise linear model for the energy function (default 10).
    :return: a tree (Concept :class:`~higra.CptHierarchy`) and its node altitudes
    """
    data_fidelity_attribute, regularization_attribute = hg.cast_to_common_type(
        data_fidelity_attribute, regularization_attribute)
    res = hg.cpp._hierarchy_to_optimal_energy_cut_hierarchy(
        tree, data_fidelity_attribute, regularization_attribute,
        int(approximation_piecewise_linear_function))
    new_tree = res.tree()
    altitudes = res.altitudes()

    hg.CptHierarchy.link(new_tree, hg.CptHierarchy.get_leaf_graph(tree))

    return new_tree, altitudes