diff --git a/doc/examples/segmentation/plot_boundary_merge.py b/doc/examples/segmentation/plot_boundary_merge.py new file mode 100644 index 00000000..e7cd89ce --- /dev/null +++ b/doc/examples/segmentation/plot_boundary_merge.py @@ -0,0 +1,82 @@ +""" +============================================ +Hierarchical Merging of Region Boundary RAGs +============================================ + +TODO: Description +""" + +from skimage import data, segmentation, filters, color +from skimage.future import graph +from matplotlib import pyplot as plt + + +def weight_boundary(graph, src, dst, n): + """ + Handle merging of nodes of a region boundary region adjacency graph. + + This function computes the `"weight"` and the count `"count"` + attributes of the edge between `n` and the node formed after + merging `src` and `dst`. + + + Parameters + ---------- + graph : RAG + The graph under consideration. + src, dst : int + The vertices in `graph` to be merged. + n : int + A neighbor of `src` or `dst` or both. + + Returns + ------- + data : dict + A dictionary with the "weight" and "count" attributes to be + assigned for the merged node. + + """ + default = {'weight': 0.0, 'count': 0} + + count_src = graph[src].get(n, default)['count'] + count_dst = graph[dst].get(n, default)['count'] + + weight_src = graph[src].get(n, default)['weight'] + weight_dst = graph[dst].get(n, default)['weight'] + + count = count_src + count_dst + return { + 'count': count, + 'weight': (count_src * weight_src + count_dst * weight_dst)/count + } + + +def merge_boundary(graph, src, dst): + """Call back called before merging 2 nodes. + + In this case we don't need to do any computation here. + """ + pass + +img = data.coffee() +edges = filters.sobel(color.rgb2gray(img)) +labels = segmentation.slic(img, compactness=30, n_segments=400) +g = graph.rag_boundary(labels, edges) + +graph.show_rag(labels, g, img) +plt.title('Initial RAG') + +labels2 = graph.merge_hierarchical(labels, g, thresh=0.08, rag_copy=False, + in_place_merge=True, + merge_func=merge_boundary, + weight_func=weight_boundary) + +graph.show_rag(labels, g, img) +plt.title('RAG after hierarchical merging') + +plt.figure() +out = color.label2rgb(labels2, img, kind='avg') +plt.imshow(out) +plt.title('Final segmentation') + +plt.show() diff --git a/doc/examples/segmentation/plot_rag.py b/doc/examples/segmentation/plot_rag.py index d4f2711c..ba6e2d3b 100644 --- a/doc/examples/segmentation/plot_rag.py +++ b/doc/examples/segmentation/plot_rag.py @@ -23,8 +23,9 @@ import numpy as np def max_edge(g, src, dst, n): """Callback to handle merging nodes by choosing maximum weight. - Returns either the weight between (`src`, `n`) or (`dst`, `n`) - in `g` or the maximum of the two when both exist. + Returns a dictionary with `"weight"` set as either the weight between + (`src`, `n`) or (`dst`, `n`) in `g` or the maximum of the two when + both exist. Parameters ---------- @@ -37,15 +38,15 @@ def max_edge(g, src, dst, n): Returns ------- - weight : float - The weight between (`src`, `n`) or (`dst`, `n`) in `g` or the - maximum of the two when both exist. - + data : dict + A dict with the "weight" attribute set the weight between + (`src`, `n`) or (`dst`, `n`) in `g` or the maximum of the two when + both exist. """ w1 = g[n].get(src, {'weight': -np.inf})['weight'] w2 = g[n].get(dst, {'weight': -np.inf})['weight'] - return max(w1, w2) + return {'weight': max(w1, w2)} def display(g, title): diff --git a/doc/examples/segmentation/plot_rag_merge.py b/doc/examples/segmentation/plot_rag_merge.py index 37712d5b..1ff68079 100644 --- a/doc/examples/segmentation/plot_rag_merge.py +++ b/doc/examples/segmentation/plot_rag_merge.py @@ -31,13 +31,14 @@ def _weight_mean_color(graph, src, dst, n): Returns ------- - weight : float - The absolute difference of the mean color between node `dst` and `n`. + data : dict + A dictionary with the `"weight"` attribute set as the absolute + difference of the mean color between node `dst` and `n`. """ diff = graph.node[dst]['mean color'] - graph.node[n]['mean color'] diff = np.linalg.norm(diff) - return diff + return {'weight': diff} def merge_mean_color(graph, src, dst): @@ -62,7 +63,7 @@ img = data.coffee() labels = segmentation.slic(img, compactness=30, n_segments=400) g = graph.rag_mean_color(img, labels) -labels2 = graph.merge_hierarchical(labels, g, thresh=40, rag_copy=False, +labels2 = graph.merge_hierarchical(labels, g, thresh=35, rag_copy=False, in_place_merge=True, merge_func=merge_mean_color, weight_func=_weight_mean_color) diff --git a/skimage/future/graph/rag.py b/skimage/future/graph/rag.py index 480ef4eb..b6ded5fe 100644 --- a/skimage/future/graph/rag.py +++ b/skimage/future/graph/rag.py @@ -50,8 +50,9 @@ def _edge_generator_from_csr(csr_matrix): def min_weight(graph, src, dst, n): """Callback to handle merging nodes by choosing minimum weight. - Returns either the weight between (`src`, `n`) or (`dst`, `n`) - in `graph` or the minimum of the two when both exist. + Returns a dictionary with `"weight"` set as either the weight between + (`src`, `n`) or (`dst`, `n`) in `graph` or the minimum of the two when + both exist. Parameters ---------- @@ -64,9 +65,10 @@ def min_weight(graph, src, dst, n): Returns ------- - weight : float - The weight between (`src`, `n`) or (`dst`, `n`) in `graph` or the - minimum of the two when both exist. + data : dict + A dict with the `"weight"` attribute set the weight between + (`src`, `n`) or (`dst`, `n`) in `graph` or the minimum of the two when + both exist. """ @@ -74,7 +76,7 @@ def min_weight(graph, src, dst, n): default = {'weight': np.inf} w1 = graph[n].get(src, default)['weight'] w2 = graph[n].get(dst, default)['weight'] - return min(w1, w2) + return {'weight': min(w1, w2)} def _add_edge_filter(values, graph): @@ -171,12 +173,12 @@ class RAG(nx.Graph): src, dst : int Nodes to be merged. weight_func : callable, optional - Function to decide edge weight of edges incident on the new node. - For each neighbor `n` for `src and `dst`, `weight_func` will be - called as follows: `weight_func(src, dst, n, *extra_arguments, + Function to decide the attributes of edges incident on the new + node. For each neighbor `n` for `src and `dst`, `weight_func` will + be called as follows: `weight_func(src, dst, n, *extra_arguments, **extra_keywords)`. `src`, `dst` and `n` are IDs of vertices in the - RAG object which is in turn a subclass of - `networkx.Graph`. + RAG object which is in turn a subclass of `networkx.Graph`. It is + expected to return a dict of attributes of the resulting edge. in_place : bool, optional If set to `True`, the merged node has the id `dst`, else merged node has a new id which is returned. @@ -207,9 +209,9 @@ class RAG(nx.Graph): self.add_node(new) for neighbor in neighbors: - w = weight_func(self, src, new, neighbor, *extra_arguments, - **extra_keywords) - self.add_edge(neighbor, new, weight=w) + data = weight_func(self, src, new, neighbor, *extra_arguments, + **extra_keywords) + self.add_edge(neighbor, new, attr_dict=data) self.node[new]['labels'] = (self.node[src]['labels'] + self.node[dst]['labels']) diff --git a/skimage/future/graph/tests/test_rag.py b/skimage/future/graph/tests/test_rag.py index 4b2e4cff..dadbe5ce 100644 --- a/skimage/future/graph/tests/test_rag.py +++ b/skimage/future/graph/tests/test_rag.py @@ -10,7 +10,7 @@ def max_edge(g, src, dst, n): default = {'weight': -np.inf} w1 = g[n].get(src, default)['weight'] w2 = g[n].get(dst, default)['weight'] - return max(w1, w2) + return {'weight': max(w1, w2)} @skipif(not is_installed('networkx')) @@ -113,7 +113,7 @@ def test_rag_error(): def _weight_mean_color(graph, src, dst, n): diff = graph.node[dst]['mean color'] - graph.node[n]['mean color'] diff = np.linalg.norm(diff) - return diff + return {'weight': diff} def _pre_merge_mean_color(graph, src, dst):