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
def binary_partition_tree_MumfordShah_energy(graph, vertex_values, vertex_area=None, vertex_perimeter=None, edge_length=None, squared_vertex_values=None): """ Binary partition tree according to the Mumford-Shah energy with a constant piecewise model. The distance between two regions is equal to the apparition scale of the merged region. 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 graph: input graph :param vertex_values: Sum of values inside each vertex of the input graph (1d array for scalar value or 2d array for vectorial values, e.g. RGB pixel values) :param vertex_area: area of the vertices of the input graph (provided by :func:`~higra.attribute_vertex_area` on `graph`) :param vertex_perimeter: perimeter of the vertices of the input graph (provided by :func:`~higra.attribute_vertex_perimeter` on `graph`) :param edge_length: length of the frontier represented by the edges of the input graph (provided by :func:`~higra.attribute_edge_length` on `graph`) :param squared_vertex_values: Sum of squared values inside each vertex of the input graph (1d array for scalar value or 2d array for vectorial values, e.g. RGB pixel values). If this argument is not provided, it will default to `vertex_values * vertex_values` which is only correct if a vertex contains a single value. :return: a tree (Concept :class:`~higra.CptHierarchy`) and its node altitudes """ if vertex_area is None: vertex_area = hg.attribute_vertex_area(graph) if edge_length is None: edge_length = hg.attribute_edge_length(graph) if vertex_perimeter is None: vertex_perimeter = hg.attribute_vertex_perimeter(graph, edge_length) vertex_values = hg.linearize_vertex_weights(vertex_values, graph) vertex_area = hg.linearize_vertex_weights(vertex_area, graph) vertex_perimeter = hg.linearize_vertex_weights(vertex_perimeter, graph) if squared_vertex_values is None: squared_vertex_values = vertex_values * vertex_values tree, altitudes = hg.cpp._binary_partition_tree_MumfordShah_energy( graph, vertex_perimeter, vertex_area, vertex_values, squared_vertex_values, edge_length) hg.CptHierarchy.link(tree, graph) return tree, altitudes
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
def component_tree_min_tree(graph, vertex_weights): """ Min Tree hierarchy from the input vertex weighted graph. The Min/Max Tree structure were proposed in [1]_, [2]_. The algorithm used in this implementation was first described in [3]_. .. [1] Ph. Salembier, A. Oliveras, and L. Garrido, "Anti-extensive connected operators for image \ and sequence processing," IEEE Trans. Image Process., vol. 7, no. 4, pp. 555-570, Apr. 1998. .. [2] Ro. Jones, "Connected filtering and segmentation using component trees," Comput. Vis. \ Image Understand., vol. 75, no. 3, pp. 215-228, Sep. 1999. .. [3] Ch. Berger, T. Geraud, R. Levillain, N. Widynski, A. Baillard, and E. Bertin, "Effective \ Component Tree Computation with Application to Pattern Recognition in Astronomical Imaging," \ IEEE ICIP 2007. :param graph: input graph :param vertex_weights: vertex 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) res = hg.cpp._component_tree_min_tree(graph, vertex_weights) tree = res.tree() altitudes = res.altitudes() hg.CptHierarchy.link(tree, graph) return tree, altitudes
def watershed_hierarchy_by_volume(graph, edge_weights, vertex_area=None): """ Watershed hierarchy by volume. The definition of hierarchical watershed follows the one given in: J. Cousty, L. Najman. `Incremental algorithm for hierarchical minimum spanning forests and saliency of watershed cuts <https://hal-upec-upem.archives-ouvertes.fr/hal-00622505/document>`_. ISMM 2011: 272-283. The algorithm used is described in: Laurent Najman, Jean Cousty, Benjamin Perret. `Playing with Kruskal: Algorithms for Morphological Trees in Edge-Weighted Graphs <https://hal.archives-ouvertes.fr/file/index/docid/798621/filename/ismm2013-algo.pdf>`_. ISMM 2013: 135-146. :param graph: input graph :param edge_weights: input graph edge weights :param vertex_area: area of the input graph vertices (provided by :func:`~higra.attribute_vertex_area`) :return: a tree (Concept :class:`~higra.CptHierarchy`) and its node altitudes """ if vertex_area is None: vertex_area = hg.attribute_vertex_area(graph) vertex_area = hg.linearize_vertex_weights(vertex_area, graph) return watershed_hierarchy_by_attribute( graph, edge_weights, lambda tree, altitudes: hg.attribute_volume( tree, altitudes, hg.attribute_area(tree, vertex_area)))
def save_graph_pink(filename, graph, vertex_weights=None, edge_weights=None, shape=None): """ Save a (vertex/edge weighted) graph in the pink ascii file format. :param filename: path to the graph file (will be overwritten if the file already exists!) :param graph: graph to save (Concept :class:`~higra.CptGridGraph`) :param edge_weights: edge weights of the graph (optional) :param vertex_weights: vertex weights of the graph (optional) :param shape: shape of the graph (optional) (deduced from :class:`~higra.CptGridGraph`) :return: nothing """ if edge_weights is None: edge_weights = np.ones((graph.num_edges(), ), dtype=np.np.float64) if vertex_weights is None: vertex_weights = np.ones((graph.num_vertices(), ), dtype=np.float64) if shape is None: shape = (graph.num_vertices(), 1) vertex_weights = hg.linearize_vertex_weights(vertex_weights, graph, shape) hg.cpp._save_graph_pink(filename, graph, vertex_weights=vertex_weights, edge_weights=edge_weights, shape=shape)
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
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
def weight_graph(graph, vertex_weights, weight_function): """ Compute the edge weights of a graph using source and target vertices values and specified weighting function (see :class:`~higra.WeightFunction` enumeration). :param graph: input graph :param vertex_weights: vertex weights of the input graph :param weight_function: see :class:`~higra.WeightFunction` :return: edge weights of the graph """ vertex_weights = hg.linearize_vertex_weights(vertex_weights, graph) edge_weights = hg.cpp._weight_graph(graph, vertex_weights, weight_function) return edge_weights
def labelisation_2_graph_cut(graph, vertex_labels): """ Determines the graph cut that corresponds to a given labeling of the graph vertices. The result is a weighting of the graph edges where edges with a non zero weight are part of the cut. :param graph: input graph :param vertex_labels: Weights on the vertices of the graph :return: graph edge-weights representing the equivalent cut """ vertex_labels = hg.linearize_vertex_weights(vertex_labels, graph) graph_cut = hg.cpp._labelisation_2_graph_cut(graph, vertex_labels) return graph_cut
def accumulate_sequential(tree, leaf_data, accumulator, leaf_graph=None): """ Sequential accumulation of node values from the leaves to the root. 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) = accumulator(output(children(i)))` :param tree: input tree (Concept :class:`~higra.CptHierarchy`) :param leaf_data: array of 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) res = hg.cpp._accumulate_sequential(tree, leaf_data, accumulator) return res
def attribute_perimeter_length_partition_tree(tree, vertex_perimeter, frontier_length, leaf_graph=None): """ Length of the perimeter of each node of the given partition tree. **Provider name**: "perimeter_length_partition_tree" :param tree: input tree (Concept :class:`~higra.CptHierarchy`) :param vertex_perimeter: perimeter length of each vertex of the leaf graph (provided by :func:`~higra.attribute_vertex_perimeter` on `leaf_graph`) :param frontier_length: length of common contour between merging regions (provided by :func:`~higra.attribute_frontier_length` on `tree`) :param leaf_graph: (deduced from :class:`~higra.CptHierarchy`) :return: a 1d array """ assert (tree.category() == hg.TreeCategory.PartitionTree) if leaf_graph is not None: vertex_perimeter = hg.linearize_vertex_weights(vertex_perimeter, leaf_graph) return hg.accumulate_and_add_sequential(tree, -2 * frontier_length, vertex_perimeter, hg.Accumulators.sum)
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)
def attribute_contour_length(tree, vertex_perimeter=None, edge_length=None, leaf_graph=None): """ Length of the contour (perimeter) of each node of the given tree. :param tree: input tree (Concept :class:`~higra.CptHierarchy`) :param vertex_perimeter: perimeter of each vertex of the leaf graph (provided by :func:`~higra.attribute_vertex_perimeter` on `leaf_graph`) :param edge_length: length of each edge of the leaf graph (provided by :func:`~higra.attribute_edge_length` on `leaf_graph`) :param leaf_graph: (deduced from :class:`~higra.CptHierarchy`) :return: a 1d array """ if vertex_perimeter is None: vertex_perimeter = hg.attribute_vertex_perimeter(leaf_graph) if edge_length is None: edge_length = hg.attribute_edge_length(leaf_graph) if leaf_graph is not None: vertex_perimeter = hg.linearize_vertex_weights(vertex_perimeter, leaf_graph) frontier_length = hg.attribute_frontier_length(tree, edge_length, leaf_graph) perimeter = hg.accumulate_and_add_sequential(tree, -2 * frontier_length, vertex_perimeter, hg.Accumulators.sum) # hg.cpp._attribute_contour_length_component_tree is more efficient than the partition tree # algorithm but it does not work for tree of shapes left in original space (the problem is that # two children of a node may become adjacent when the interpolated pixels are removed). # if tree.category() == hg.TreeCategory.PartitionTree: # frontier_length = hg.attribute_frontier_length(tree, edge_length, leaf_graph) # perimeter = hg.accumulate_and_add_sequential(tree, -2 * frontier_length, vertex_perimeter, # hg.Accumulators.sum) # elif tree.category() == hg.TreeCategory.ComponentTree: # perimeter = hg.cpp._attribute_contour_length_component_tree(tree, leaf_graph, vertex_perimeter, # edge_length) return perimeter
def component_tree_max_tree(graph, vertex_weights): """ Max Tree hierarchy from the input vertex weighted graph. The Min/Max Tree structure were proposed in [1]_, [2]_. The algorithm used in this implementation was first described in [3]_. :param graph: input graph :param vertex_weights: vertex 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) tree, altitudes = hg.cpp._component_tree_max_tree(graph, vertex_weights) hg.CptHierarchy.link(tree, graph) return tree, altitudes
def attribute_perimeter_length_component_tree(tree, vertex_perimeter, edge_length, leaf_graph): """ Length of the perimeter of each node of the given component tree. **Provider name**: "perimeter_length_component_tree" :param tree: input tree (Concept :class:`~higra.CptHierarchy`) :param vertex_perimeter: perimeter length of each vertex of the leaf graph (provided by :func:`~higra.attribute_vertex_perimeter` on `leaf_graph`) :param edge_length: length of each edge of the leaf graph (provided by :func:`~higra.attribute_edge_length` on `leaf_graph`) :param leaf_graph: (deduced from :class:`~higra.CptHierarchy`) :return: a 1d array """ assert (tree.category() == hg.TreeCategory.ComponentTree) if leaf_graph is not None: vertex_perimeter = hg.linearize_vertex_weights(vertex_perimeter, leaf_graph) res = hg.cpp._attribute_perimeter_length_component_tree(tree, leaf_graph, vertex_perimeter, edge_length) return res
def make_region_adjacency_graph_from_labelisation(graph, vertex_labels): """ Create a region adjacency graph (rag) of a vertex labelled graph. Each maximal connected set of vertices having the same label is a region. Each region is represented by a vertex in the rag. There is an edge between two regions of labels :math:`l_1` and :math:`l_2` in the rag iff there exists an edge linking two vertices of labels :math:`l_1` and :math:`l_2` int he original graph. :param graph: input graph :param vertex_labels: vertex labels on the input graph :return: a region adjacency graph (Concept :class:`~higra.CptRegionAdjacencyGraph`) """ vertex_labels = hg.linearize_vertex_weights(vertex_labels, graph) rag, vertex_map, edge_map = hg.cpp._make_region_adjacency_graph_from_labelisation( graph, vertex_labels) hg.CptRegionAdjacencyGraph.link(rag, graph, vertex_map, edge_map) return rag
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
def rag_accumulate_on_vertices(rag, accumulator, vertex_weights): """ Weights rag vertices by accumulating values from the vertex weights of the original graph. For any vertex index :math:`i` of the rag, :math:`result[i] = accumulator(\{vertex\_weights[j] | rag\_vertex\_map[j] == i\})` :param rag: input region adjacency graph (Concept :class:`~higra.RegionAdjacencyGraph`) :param vertex_weights: vertex weights on the original graph :param accumulator: see :class:`~higra.Accumulators` :return: vertex weights on the region adjacency graph """ detail = hg.CptRegionAdjacencyGraph.construct(rag) vertex_weights = hg.linearize_vertex_weights(vertex_weights, detail["pre_graph"]) new_weights = hg.cpp._rag_accumulate(detail["vertex_map"], vertex_weights, accumulator) return new_weights
def labelisation_seeded_watershed(graph, edge_weights, vertex_seeds, background_label=0): """ Seeded watershed cut on an edge weighted graph. Seeds and associated labels are given in :attr:`vertex_seeds`. A vertex :math:`v`, such that :math:`vertex\_seeds(v)\\neq background\_label` is a seed with associated label :math:`vertex\_seeds(v)`. The label of a vertex of the graph is then defined equal to the label of the closest seed in the edge weighted graph for the min-max distance. If several such seeds exist (eg. on a plateus between two seeds), an arbitrary and consistent choice is made ensuring that: - each flat zone of level :math:`k` of the final labelling contains at least one seed with the label :math:`k`; and - each seed is contained in a flat zone whose level is equal to the seed label. :Complexity: This algorithm has a runtime complexity in :math:`\mathcal{O}(n \log n)` with :math:`n` the number of edges in the graph. :param graph: Input graph :param edge_weights: Weights on the edges of the graph :param vertex_seeds: Seeds with integer label values on the vertices of the graph :param background_label: Vertices whose values are equal to :attr:`background_label` (default 0) in :attr:`vertex_seeds` are not considered as seeds :return: A labelisation of the graph vertices """ if not issubclass(vertex_seeds.dtype.type, np.integer): raise ValueError("vertex_seeds must be an array of integers") vertex_seeds = hg.linearize_vertex_weights(vertex_seeds, graph) vertex_seeds = hg.cast_to_dtype(vertex_seeds, np.int64) labels = hg.cpp._labelisation_seeded_watershed(graph, edge_weights, vertex_seeds, background_label) labels = hg.delinearize_vertex_weights(labels, graph) return labels
def test_linearize_vertex_weights(self): g = hg.get_4_adjacency_graph((4, 5)) r = hg.linearize_vertex_weights(np.ones((20, )), g, (4, 5)) self.assertTrue(r.shape == (20, )) r = hg.linearize_vertex_weights(np.ones((4, 5)), g, (4, 5)) self.assertTrue(r.shape == (20, )) r = hg.linearize_vertex_weights(np.ones((4, 5, 10, 12)), g, (4, 5)) self.assertTrue(r.shape == (20, 10, 12)) r = hg.linearize_vertex_weights(np.ones((20, 4, 5, 2, 3)), g, (4, 5)) self.assertTrue(r.shape == (20, 4, 5, 2, 3)) with self.assertRaises(ValueError): hg.linearize_vertex_weights(np.ones((5, 4)), g, (4, 5)) with self.assertRaises(ValueError): hg.linearize_vertex_weights(np.ones((25, )), g, (4, 5)) g = hg.get_4_adjacency_graph((4, 1)) r = hg.linearize_vertex_weights(np.ones((4, )), g) self.assertTrue(r.shape == (4, )) r = hg.linearize_vertex_weights(np.ones((4, 1)), g) self.assertTrue(r.shape == (4, )) r = hg.linearize_vertex_weights(np.ones((4, 1, 10, 12)), g) self.assertTrue(r.shape == (4, 10, 12)) r = hg.linearize_vertex_weights(np.ones((4, 4, 5, 2, 3)), g) self.assertTrue(r.shape == (4, 4, 5, 2, 3)) with self.assertRaises(ValueError): hg.linearize_vertex_weights(np.ones((5, 4)), g, (4, 5)) with self.assertRaises(ValueError): hg.linearize_vertex_weights(np.ones((25, )), g, (4, 5)) g = hg.get_4_adjacency_graph((1, 4)) r = hg.linearize_vertex_weights(np.ones((4, )), g) self.assertTrue(r.shape == (4, )) r = hg.linearize_vertex_weights(np.ones((1, 4)), g) self.assertTrue(r.shape == (4, )) r = hg.linearize_vertex_weights(np.ones((1, 4, 10, 12)), g) self.assertTrue(r.shape == (4, 10, 12)) r = hg.linearize_vertex_weights(np.ones((4, 4, 5, 2, 3)), g) self.assertTrue(r.shape == (4, 4, 5, 2, 3)) with self.assertRaises(ValueError): hg.linearize_vertex_weights(np.ones((5, 4)), g, (4, 5)) with self.assertRaises(ValueError): hg.linearize_vertex_weights(np.ones((25, )), g, (4, 5))
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