Ejemplo n.º 1
0
def attribute_gaussian_region_weights_model(tree,
                                            vertex_weights,
                                            leaf_graph=None):
    """
    Estimates a gaussian model (mean, (co-)variance) for leaf weights inside a node.

    The result is composed of two arrays:

        - the first one contains the mean value inside each node, scalar if vertex weights are scalar and vectorial otherwise,
        - the second one contains the variance of the values inside each node, scalar if vertex weights are scalar and a (biased) covariance matrix otherwise.

    Vertex weights must be scalar or 1 dimensional.

    :param tree: input tree (Concept :class:`~higra.CptHierarchy`)
    :param vertex_weights: vertex weights of the leaf graph of the input tree
    :param leaf_graph: leaf graph of the input tree (deduced from :class:`~higra.CptHierarchy`)
    :return: two arrays mean and variance
    """
    if leaf_graph is not None:
        vertex_weights = hg.linearize_vertex_weights(vertex_weights,
                                                     leaf_graph)

    if vertex_weights.ndim > 2:
        raise ValueError(
            "Vertex weight can either be scalar or 1 dimensional.")

    if vertex_weights.dtype not in (np.float32, np.float64):
        vertex_weights = vertex_weights.astype(np.float64)

    area = hg.attribute_area(tree, leaf_graph=leaf_graph)
    mean = hg.accumulate_sequential(tree, vertex_weights, hg.Accumulators.sum,
                                    leaf_graph)

    if vertex_weights.ndim == 1:
        # general case below would work but this is simpler
        mean /= area
        mean2 = hg.accumulate_sequential(tree, vertex_weights * vertex_weights,
                                         hg.Accumulators.sum, leaf_graph)
        mean2 /= area
        variance = mean2 - mean * mean
    else:
        mean /= area[:, None]
        tmp = vertex_weights[:, :, None] * vertex_weights[:, None, :]
        mean2 = hg.accumulate_sequential(tree, tmp, hg.Accumulators.sum,
                                         leaf_graph)
        mean2 /= area[:, None, None]

        variance = mean2 - mean[:, :, None] * mean[:, None, :]

    return mean, variance
Ejemplo n.º 2
0
def attribute_mean_vertex_weights(tree, vertex_weights, area=None, leaf_graph=None):
    """
    Mean vertex weights of the leaf graph vertices inside each node of the given tree.

    For any node :math:`n`, the mean vertex weights :math:`a(n)` of :math:`n` is

    .. math::

        a(n) = \\frac{\sum_{x\in n} vertex\_weights(x)}{area(n)}

    :param tree: input tree (Concept :class:`~higra.CptHierarchy`)
    :param vertex_weights: vertex weights of the leaf graph of the input tree
    :param area: area of the tree nodes  (provided by :func:`~higra.attribute_area`)
    :param leaf_graph: leaf graph of the input tree (deduced from :class:`~higra.CptHierarchy`)
    :return: a nd array
    """
    if area is None:
        area = hg.attribute_area(tree)

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

    attribute = hg.accumulate_sequential(
        tree,
        vertex_weights.astype(np.float64),
        hg.Accumulators.sum) / area.reshape([-1] + [1] * (vertex_weights.ndim - 1))
    return attribute
Ejemplo n.º 3
0
def dendrogram_purity_naif(tree, leaf_labels):
    from itertools import combinations

    tree.lowest_common_ancestor_preprocess()
    area = hg.attribute_area(tree)
    max_label = np.max(leaf_labels)
    label_histo = np.zeros((tree.num_leaves(), max_label + 1), dtype=np.int64)
    label_histo[np.arange(tree.num_leaves()), leaf_labels] = 1
    label_histo = hg.accumulate_sequential(tree, label_histo,
                                           hg.Accumulators.sum)
    class_purity = label_histo / area[:, None]

    count = 0
    total = 0
    for label in set(leaf_labels):
        same = leaf_labels == label
        same_indices, = same.nonzero()

        if len(same_indices) < 2:
            continue

        pairs = list(combinations(same_indices, 2))
        count += len(pairs)

        pairs = np.asarray(pairs, dtype=np.int64)
        lcas = tree.lowest_common_ancestor(pairs[:, 0], pairs[:, 1])
        total += np.sum(class_purity[lcas, label])

    return total / count
Ejemplo n.º 4
0
def labelisation_seeded_watershed(graph, edge_weights, vertex_seeds):
    """
    Seeded watershed cut on an edge weighted graph.
    Seeds are defined as vertex weights: any flat zone of value strictly greater than 0 is considered as a seed.

    Note that if two different seeds are places in a minima of the edge weighted graph, and if the altitude of this minima
    is equal to the smallest representable value for the given `dtype` of the edge weights, then the algorithm won't be able
    to produce two different regions for these two seeds.

    :param graph: input graph
    :param edge_weights: Weights on the edges of the graph
    :param vertex_seeds: Seeds on the vertices of the graph
    :return: A labelisation of the graph vertices
    """
    # edges inside a seed take the value of the seed and 0 otherwise
    edges_in_or_between_seeds = hg.weight_graph(graph, vertex_seeds,
                                                hg.WeightFunction.L0)
    edges_outside_seeds = hg.weight_graph(graph, vertex_seeds,
                                          hg.WeightFunction.min)
    edges_in_seed = np.logical_and(edges_outside_seeds > 0,
                                   1 - edges_in_or_between_seeds)

    # set edges inside seeds at minimum level
    edge_weights = edge_weights.copy()
    edge_weights[edges_in_seed > 0] = hg.dtype_info(edge_weights.dtype).min

    tree, altitudes = hg.watershed_hierarchy_by_attribute(
        graph, edge_weights, lambda tree, _: hg.accumulate_sequential(
            tree, vertex_seeds, hg.Accumulators.max))

    return hg.labelisation_hierarchy_supervertices(tree, altitudes)
Ejemplo n.º 5
0
    def test_tree_accumulator(self):
        tree = TestTreeAccumulators.get_tree()
        input_array = np.asarray((1, 1, 1, 1, 1, 1, 1, 1))

        res1 = hg.accumulate_parallel(tree, input_array, hg.Accumulators.sum)
        ref1 = np.asarray((0, 0, 0, 0, 0, 2, 3, 2))
        self.assertTrue(np.allclose(ref1, res1))

        leaf_data = np.asarray((1, 1, 1, 1, 1))
        res2 = hg.accumulate_sequential(tree, leaf_data, hg.Accumulators.sum)
        ref2 = np.asarray((1, 1, 1, 1, 1, 2, 3, 5))
        self.assertTrue(np.allclose(ref2, res2))

        res3 = hg.accumulate_and_add_sequential(tree, input_array, leaf_data, hg.Accumulators.max)
        ref3 = np.asarray((1, 1, 1, 1, 1, 2, 2, 3))
        self.assertTrue(np.allclose(ref3, res3))

        input_array = np.asarray((1, 2, 1, 2, 1, 1, 4, 5))
        res4 = hg.accumulate_and_max_sequential(tree, input_array, leaf_data, hg.Accumulators.sum)
        ref4 = np.asarray((1, 1, 1, 1, 1, 2, 4, 6))
        self.assertTrue(np.allclose(ref4, res4))

        input_array = np.asarray((1, 2, 1, 2, 1, 2, 3, 1))
        res5 = hg.accumulate_and_multiply_sequential(tree, input_array, leaf_data, hg.Accumulators.sum)
        ref5 = np.asarray((1, 1, 1, 1, 1, 4, 9, 13))
        self.assertTrue(np.allclose(ref5, res5))

        input_array = np.asarray((1, 2, 1, 2, 1, 4, 2, 10))
        res6 = hg.accumulate_and_min_sequential(tree, input_array, leaf_data, hg.Accumulators.sum)
        ref6 = np.asarray((1, 1, 1, 1, 1, 2, 2, 4))
        self.assertTrue(np.allclose(ref6, res6))
Ejemplo n.º 6
0
def dendrogram_purity(tree, leaf_labels):
    """
    Weighted average of the purity of each node of the tree with respect to a ground truth
    labelization of the tree leaves.
    
    Let :math:`T` be a tree with leaves :math:`V=\{1, \ldots, n\}`.
    Let :math:`C=\{C_1, \ldots, C_K\}` be a partition of :math:`V` into :math:`k` (label) sets.
    
    The purity of a subset :math:`X` of :math:`V` with respect to class :math:`C_\ell\in C` is the fraction of
    elements of :math:`X` that belongs to class :math:`C_\ell`:
    
    .. math::
    
         pur(X, C_\ell) = \\frac{| X \cap C_\ell |}{| X |}.
    
    The purity of :math:`T` is the defined as:
    
    .. math::
    
         pur(T) = \\frac{1}{Z}\sum_{k=1}^{K}\sum_{x,y\in C_k, x\\neq y} pur(lca_T(x,y), C_k)
    
    with :math:`Z=| \{\{x,y\} \subseteq V \mid x\\neq y, \exists k, \{x,y\}\subseteq C_k\} |`.
    
    :See:
    
         Heller, Katherine A., and Zoubin Ghahramani. "`Bayesian hierarchical clustering <https://www2.stat.duke.edu/~kheller/bhcnew.pdf>`_ ."
         Proc. ICML. ACM, 2005.
    
    :Complexity:
    
    The dendrogram purity is computed in :math:`\mathcal{O}(N\\times K \\times C^2)` with :math:`N` the number of nodes
    in the tree, :math:`K` the number of classes, and :math:`C` the maximal number of children of a node in the tree.

    :param tree: input tree
    :param leaf_labels: a 1d integral array of length `tree.num_leaves()`
    :return:  a score between 0 and 1 (higher is better)
    """
    if leaf_labels.ndim != 1 or leaf_labels.size != tree.num_leaves(
    ) or leaf_labels.dtype.kind != 'i':
        raise ValueError(
            "leaf_labels must be a 1d integral array of length `tree.num_leaves()`"
        )

    num_l = tree.num_leaves()
    area = hg.attribute_area(tree)

    max_label = np.max(leaf_labels)
    num_labels = max_label + 1
    label_histo_leaves = np.zeros((num_l, num_labels), dtype=np.float64)
    label_histo_leaves[np.arange(num_l), leaf_labels] = 1

    label_histo = hg.accumulate_sequential(tree, label_histo_leaves,
                                           hg.Accumulators.sum)
    class_purity = label_histo / area[:, np.newaxis]

    weights = hg.attribute_children_pair_sum_product(tree, label_histo)
    total = np.sum(class_purity[num_l:, :] * weights[num_l:, :])

    return total / np.sum(weights[num_l:])
Ejemplo n.º 7
0
def propagate_weights(tree, label_map, weight_map):
    # tree: input tree where leaves are superpixels
    # label_map: 2d array of integers of shape (M,N)
    # weight_map: 2d array of shape (M,N)
    #
    # return: propagated probabilities on each node of the tree

    regions = regionprops(label_map + 1, intensity_image=weight_map)
    # get area of all nodes
    area_leaves = np.array([p['area'] for p in regions])
    areas = hg.accumulate_sequential(tree, area_leaves, hg.Accumulators.sum)

    # get sum of proba on each leaf
    means = np.array([p['mean_intensity'] for p in regions])
    sums = means * area_leaves

    attribute = hg.accumulate_sequential(
        tree, sums, hg.Accumulators.sum) / areas.reshape([-1] + [1] *
                                                         (sums.ndim - 1))
    return attribute
Ejemplo n.º 8
0
def candidate_subtrees(tree, label_map, leaves_weights, clicked_coords=[]):
    # clicked_coords is a list of ij coordinates
    # returns list [(accumulated_weight, [l0, ...])]
    # where [l0, ...] are reachable leaves
    # leaves_weights will be propagated.
    # 1d array: It is taken as an area-normalized (average pooling) metric
    # 2d array: It will be average pooled according to label_map
    #
    # returns:
    #  - weights of each node
    #  - indices of parent nodes that are eligible (from which one can reach clicked)

    leaves = np.unique(label_map)
    regions = regionprops(label_map + 1)

    if (leaves_weights.ndim == 2):
        regions = regionprops(label_map + 1, intensity_image=leaves_weights)
        leaves_weights = np.array([p['mean_intensity'] for p in regions])
    elif (leaves_weights.ndim == 1):
        pass
    else:
        raise TypeError('leaves_weights: Only 1-D and 2-D arrays supported.')

    area_leaves = np.array([p['area'] for p in regions])
    areas = hg.accumulate_sequential(tree, area_leaves, hg.Accumulators.sum)
    sums = leaves_weights * area_leaves

    # each node is area-weighted sum of weights
    accum_weights = hg.accumulate_sequential(
        tree, sums, hg.Accumulators.sum) / areas.reshape([-1] + [1] *
                                                         (sums.ndim - 1))

    # get list [(a, [l0, ...])] where a is a candidate ancestor and [l0, ...] are its leaves

    parents = []
    for r in clicked_coords:
        clicked_label = label_map[tuple(r)]
        parents.extend(tree.ancestors(clicked_label)[1:])

    return accum_weights, np.array(parents)
Ejemplo n.º 9
0
    def _get_associated_mst(tree, altitudes):
        """
        Create a valid edge mst for the given tree (returns an edge weighted undirected graph)
        """
        nb = tree.num_leaves()
        link_v = np.arange(nb)
        link_v = hg.accumulate_sequential(tree, link_v, hg.Accumulators.first)

        g = hg.UndirectedGraph(nb)
        edge_weights = np.zeros((nb - 1,), np.float32)
        for r in tree.leaves_to_root_iterator(include_leaves=False):
            g.add_edge(link_v[tree.child(0, r)], link_v[tree.child(1, r)])
            edge_weights[r - nb] = altitudes[r]

        return g, edge_weights
Ejemplo n.º 10
0
    def test_tree_accumulatorVec(self):
        tree = TestTreeAccumulators.get_tree()
        input_array = np.asarray(((1, 0),
                                  (1, 1),
                                  (1, 2),
                                  (1, 3),
                                  (1, 4),
                                  (1, 5),
                                  (1, 6),
                                  (1, 7)))

        res1 = hg.accumulate_parallel(tree, input_array, hg.Accumulators.sum)
        ref1 = np.asarray(((0, 0),
                           (0, 0),
                           (0, 0),
                           (0, 0),
                           (0, 0),
                           (2, 1),
                           (3, 9),
                           (2, 11)))
        self.assertTrue(np.allclose(ref1, res1))

        leaf_data = np.asarray(((1, 0),
                                (1, 1),
                                (1, 2),
                                (1, 3),
                                (1, 4)))
        res2 = hg.accumulate_sequential(tree, leaf_data, hg.Accumulators.sum)
        ref2 = np.asarray(((1, 0),
                           (1, 1),
                           (1, 2),
                           (1, 3),
                           (1, 4),
                           (2, 1),
                           (3, 9),
                           (5, 10)))
        self.assertTrue(np.allclose(ref2, res2))

        res3 = hg.accumulate_and_add_sequential(tree, input_array, leaf_data, hg.Accumulators.sum)
        ref3 = np.asarray(((1, 0),
                           (1, 1),
                           (1, 2),
                           (1, 3),
                           (1, 4),
                           (3, 6),
                           (4, 15),
                           (8, 28)))
        self.assertTrue(np.allclose(ref3, res3))
Ejemplo n.º 11
0
def attribute_area(tree, vertex_area=None, leaf_graph=None):
    """
    Area of each node the given tree.
    The area of a node is equal to the sum of the area of the leaves of the subtree rooted in the node.

    **Provider name**: "area"

    :param tree: input tree (Concept :class:`~higra.CptHierarchy`)
    :param vertex_area: area of the vertices of the leaf graph of the tree (provided by :func:`~higra.attribute_vertex_area` on `leaf_graph` )
    :param leaf_graph: (deduced from :class:`~higra.CptHierarchy`)
    :return: a 1d array
    """
    if vertex_area is None:
        vertex_area = np.ones((tree.num_leaves(),), dtype=np.float64)

    if leaf_graph is not None:
        vertex_area = hg.linearize_vertex_weights(vertex_area, leaf_graph)
    return hg.accumulate_sequential(tree, vertex_area, hg.Accumulators.sum)
Ejemplo n.º 12
0
def attribute_mean_weights(tree, vertex_weights, area, leaf_graph=None):
    """
    Mean weight of the leaf graph vertices inside each node of the given tree.

    **Provider name**: "mean_weights"

    :param tree: input tree (Concept :class:`~higra.CptHierarchy`)
    :param vertex_weights: vertex weights of the leaf graph of the input tree
    :param area: area of the tree nodes  (provided by :func:`~higra.attribute_area` on `tree`)
    :param leaf_graph: leaf graph of the input tree (deduced from :class:`~higra.CptHierarchy`)
    :return: a nd array
    """

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

    attribute = hg.accumulate_sequential(
        tree,
        vertex_weights.astype(np.float64),
        hg.Accumulators.sum) / area.reshape((-1, 1))
    return attribute
Ejemplo n.º 13
0
def attribute_tree_sampling_probability(tree, leaf_graph, leaf_graph_edge_weights, model='edge'):
    """
    Given a tree :math:`T`, estimate the probability that a node :math:`n` of the tree represents the smallest cluster
    containing a pair of vertices :math:`\{a, b\}` of the graph :math:`G=(V, E)`
    with edge weights :math:`w`.

    This method is defined in [1]_.

    We define the probability :math:`P(\{a,b\})` of a pair of vertices :math:`\{a,b\}` as :math:`w(\{a,b\}) / Z`
    with :math:`Z=\sum_{e\in E}w(E)` if :math:`\{a,b\}` is an edge of :math:`G` and 0 otherwise.
    Then the probability :math:`P(a)` of a vertex :math:`b` is defined as :math:`\sum_{b\in V}P(\{a, b\})`

    Two sampling strategies are proposed for sampling pairs of vertices to compute the probability of a node of the tree:

    - *edge*: the probability of sampling the pair :math:`\{a, b\}` is given by :math:`P(\{a, b\})`; and
    - *null*: the probability of sampling the pair :math:`\{a, b\}` is given by the product of the probabilities
      of :math:`a` and :math:`b`: :math:`P(a)*P(b)`.

    Assuming that the edge weights on the leaf graph of a hierarchy represents similarities:

    .. epigraph::

        *We expect these distributions to differ significantly if the tree indeed represents the hierarchical structure of the graph.
        Specifically, we expect [the edge distribution] to be mostly concentrated on deep nodes of the tree
        (far from the root), as two nodes* :math:`u`, :math:`v` *connected with high weight* :math:`w(\{u, v\})` *in the graph
        typically  belong to a small cluster, representative of the clustering structure of the graph; on the contrary,
        we expect [the null distribution] to be concentrated over shallow nodes (close to the root) as two nodes*
        :math:`w(\{u, v\})` *sampled independently at random typically belong to large clusters, less representative of the
        clustering structure of the graph*. [1]_


    .. [1] Charpentier, B. & Bonald, T. (2019).  `"Tree Sampling Divergence: An Information-Theoretic Metric for \
           Hierarchical Graph Clustering." <https://hal.telecom-paristech.fr/hal-02144394/document>`_ Proceedings of IJCAI.

    :Complexity:

    The tree sampling divergence runtime complexity depends of the sampling model:

     - *edge*: :math:`\mathcal{O}(N\log(N) + M)` with :math:`N` the number of  nodes in the tree and :math:`M` the number of edges in the leaf graph.
     - *null*: :math:`\mathcal{O}(N\\times C^2)` with :math:`N` the number of nodes in the tree  and :math:`C` the maximal number of children of a node in the tree.

    :see:

    The :func:`~higra.tree_sampling_divergence` is a non supervised hierarchical cost function defined as the
    Kullback-Leibler divergence between the edge sampling model and the independent (null) sampling model.

    :param tree: Input tree
    :param leaf_graph: Graph defined on the leaves of the input tree
    :param leaf_graph_edge_weights: Edge weights of the leaf graphs (similarities)
    :param model: defines the edge sampling strategy, either "edge" or "null"
    :return: a 1d array
    """
    if model not in ("edge", "null"):
        raise ValueError("Parameter 'model' must be either 'edge' or 'null'.")

    if model == 'edge':
        lca_map = hg.attribute_lca_map(tree, leaf_graph=leaf_graph)
        leaf_graph_edge_weights = leaf_graph_edge_weights / np.sum(leaf_graph_edge_weights)
        return hg.accumulate_at(lca_map, leaf_graph_edge_weights, hg.Accumulators.sum)
    else:  # model = 'null'
        leaf_graph_vertex_weights = hg.accumulate_graph_edges(leaf_graph, leaf_graph_edge_weights, hg.Accumulators.sum)
        leaf_graph_vertex_weights = leaf_graph_vertex_weights / np.sum(leaf_graph_edge_weights)
        tree_node_weights = hg.accumulate_sequential(tree, leaf_graph_vertex_weights, hg.Accumulators.sum)
        return hg.attribute_children_pair_sum_product(tree, tree_node_weights)
Ejemplo n.º 14
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
Ejemplo n.º 15
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
Ejemplo n.º 16
0
def attribute_moment_of_inertia(tree, leaf_graph):
    """
    Moment of inertia (first Hu moment) of each node of the given tree.
    This function works only if :attr:`leaf_graph` is a 2D grid graph.
    The moment of inertia is a translation, scale and rotation invariant characterization of the shape of the nodes.

    Given a node :math:`X` of :attr:`tree`, the raw moments :math:`M_{ij}` are defined as:

    .. math::

        M_{ij} = \sum_{x}\sum_{y} x^i y^j

    where :math:`(x,y)` are the coordinates of every vertex in :math:`X`.
    Then, the centroid :math:`\{\overline{x},\overline{y}\}` of :math:`X` is given by

    .. math::

         \overline{x} = \\frac{M_{10}}{M_{00}} \\textrm{ and  } \overline{y} = \\frac{M_{01}}{M_{00}}

    Some central moments of :math:`X` are then:

    - :math:`\mu_{00} = M_{00}`
    - :math:`\mu_{20} = M_{20} - \overline{x} \\times M_{10}`
    - :math:`\mu_{02} = M_{02} - \overline{y} \\times M_{01}`

    The moment of inertia :math:`I_1` of :math:`X` if finally defined as

    .. math::

        I_1 = \eta_{20} + \eta_{02}
        
    where :math:`\eta_{ij}` are given by:
    
        :math:`\eta_{ij} = \\frac{\mu_{ij}}{\mu_{00}^{1+\\frac{i+j}{2}}}`

    :param tree: input tree (Concept :class:`~higra.CptHierarchy`)
    :param leaf_graph: graph on the leaves of the input tree (deduced from :class:`~higra.CptHierarchy` on `tree`)
    :return: a 1d array
    """

    if (not hg.CptGridGraph.validate(leaf_graph)) or (len(
            hg.CptGridGraph.get_shape(leaf_graph)) != 2):
        raise ValueError("Parameter 'leaf_graph' must be a 2D grid graph.")

    coordinates = hg.attribute_vertex_coordinates(leaf_graph)
    coordinates = np.reshape(
        coordinates,
        (coordinates.shape[0] * coordinates.shape[1], coordinates.shape[2]))

    M_00_leaves = np.ones((tree.num_leaves())).astype(dtype=np.float64)
    x_leaves = coordinates[:, 0].astype(dtype=np.float64)
    y_leaves = coordinates[:, 1].astype(dtype=np.float64)

    M_10_leaves = x_leaves * M_00_leaves
    M_01_leaves = y_leaves * M_00_leaves
    M_20_leaves = (x_leaves**2) * M_00_leaves
    M_02_leaves = (y_leaves**2) * M_00_leaves

    M_00 = hg.accumulate_sequential(tree, M_00_leaves, hg.Accumulators.sum)
    M_01 = hg.accumulate_sequential(tree, M_01_leaves, hg.Accumulators.sum)
    M_10 = hg.accumulate_sequential(tree, M_10_leaves, hg.Accumulators.sum)
    M_02 = hg.accumulate_sequential(tree, M_02_leaves, hg.Accumulators.sum)
    M_20 = hg.accumulate_sequential(tree, M_20_leaves, hg.Accumulators.sum)

    _x = M_10 / M_00
    _y = M_01 / M_00
    miu_20 = M_20 - _x * M_10
    miu_02 = M_02 - _y * M_01
    I_1 = (miu_20 + miu_02) / (M_00**2)

    return I_1