From 3973b3030039c05bedefba5e70464dfb66af2250 Mon Sep 17 00:00:00 2001 From: Vighnesh Birodkar Date: Sat, 9 Aug 2014 21:29:41 +0530 Subject: [PATCH] docstring changes and code movement --- skimage/graph/_ncut_cy.pyx | 2 - skimage/graph/graph_cut.py | 84 +++++++++++++++++++++++++------------- skimage/graph/rag.py | 17 ++++---- 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/skimage/graph/_ncut_cy.pyx b/skimage/graph/_ncut_cy.pyx index 0888be72..de3b5c82 100644 --- a/skimage/graph/_ncut_cy.pyx +++ b/skimage/graph/_ncut_cy.pyx @@ -74,7 +74,5 @@ def cut_cost(cut, W): row = indices[row_index] if cut_mask[row] != cut_mask[col]: cost += data[row_index] - row_index += 1 - col += 1 return cost * 0.5 diff --git a/skimage/graph/graph_cut.py b/skimage/graph/graph_cut.py index b86f6c48..1e3d31dc 100644 --- a/skimage/graph/graph_cut.py +++ b/skimage/graph/graph_cut.py @@ -9,7 +9,7 @@ from . import _ncut_cy from scipy.sparse import linalg -def cut_threshold(labels, rag, thresh): +def cut_threshold(labels, rag, thresh, in_place=True): """Combine regions seperated by weight less than threshold. Given an image's labels and its RAG, output new labels by @@ -25,6 +25,10 @@ def cut_threshold(labels, rag, thresh): thresh : float The threshold. Regions connected by edges with smaller weights are combined. + in_place : bool + If set, modifies `rag` in place. The function will remove the edges + with weights less that `thresh`. If set to `False` the function + makes a copy of `rag` before proceeding. Returns ------- @@ -47,7 +51,9 @@ def cut_threshold(labels, rag, thresh): """ # Because deleting edges while iterating through them produces an error. - rag = rag.copy() + if not in_place: + rag = rag.copy() + to_remove = [(x, y) for x, y, d in rag.edges_iter(data=True) if d['weight'] >= thresh] rag.remove_edges_from(to_remove) @@ -66,7 +72,7 @@ def cut_threshold(labels, rag, thresh): return map_array[labels] -def cut_normalized(labels, rag, thresh=0.001, num_cuts=10): +def cut_normalized(labels, rag, thresh=0.001, num_cuts=10, in_place=True): """Perform Normalized Graph cut on the Region Adjacency Graph. Given an image's labels and its similarity RAG, recursively perform @@ -85,6 +91,9 @@ def cut_normalized(labels, rag, thresh=0.001, num_cuts=10): value of the N-cut exceeds `thresh`. num_cuts : int The number or N-cuts to perform before determining the optimal one. + in_place : bool + If set, modifies `rag` in place. For each node `n` the function will + set a new attribute ``rag.node[n]['ncut label]``. Returns ------- @@ -106,8 +115,15 @@ def cut_normalized(labels, rag, thresh=0.001, num_cuts=10): IEEE Transactions on , vol.22, no.8, pp.888,905, August 2000 """ - map_array = np.arange(labels.max() + 1) - _ncut_relabel(rag, thresh, num_cuts, map_array) + if not in_place: + rag = rag.copy() + + _ncut_relabel(rag, thresh, num_cuts) + + map_array = np.zeros(labels.max() + 1) + # Mapping from old labels to new + for n, d in rag.nodes_iter(data=True): + map_array[d['labels']] = d['ncut label'] return map_array[labels] @@ -128,6 +144,16 @@ def partition_by_cut(cut, rag): sub1, sub2 : RAG The two resulting subgraphs from the bi-partition. """ + # `cut` is derived from `D` and `W` matrices, which also follow the + # ordering returned by `rag.nodes()` because we use + # nx.to_scipy_sparce_matrix. + + # Example + # rag.nodes() = [3, 7, 9, 13] + # cut = [True, False, True, False] + # nodes1 = [3, 9] + # nodes2 = [7, 10] + nodes1 = [n for i, n in enumerate(rag.nodes()) if cut[i]] nodes2 = [n for i, n in enumerate(rag.nodes()) if not cut[i]] @@ -153,9 +179,10 @@ def get_min_ncut(ev, d, w, num_cuts): Returns ------- - threshold, mcut : float - The threshold which produced the minimum ncut, and the value of the - ncut itself. + mask : array + The array of booleans which denotes the bi-partition. + mcut : float + The value of the minimum ncut. """ mcut = np.inf @@ -165,13 +192,21 @@ def get_min_ncut(ev, d, w, num_cuts): mask = ev > t cost = _ncut.ncut_cost(mask, d, w) if cost < mcut: + min_mask = mask mcut = cost - threshold = t - return threshold, mcut + return min_mask, mcut -def _ncut_relabel(rag, thresh, num_cuts, map_array): +def _label_all(rag, attr_name): + node = rag.nodes()[0] + new_label = rag.node[node]['labels'][0] + for n, d in rag.nodes_iter(data=True): + for l in d['labels']: + d[attr_name] = new_label + + +def _ncut_relabel(rag, thresh, num_cuts): """Perform Normalized Graph cut on the Region Adjacency Graph. Recursively partition the graph into 2, until further subdivision @@ -195,46 +230,37 @@ def _ncut_relabel(rag, thresh, num_cuts, map_array): the function. """ d, w = _ncut.DW_matrices(rag) - stop = False m = w.shape[0] if m > 2: d2 = d.copy() # Since d is diagonal, we can directly operate on it's data - # the inverse - d2.data = 1.0 / d2.data - # the square root - d2.data = np.sqrt(d2.data) + # the inverse of the square root + d2.data = np.reciprocal(np.sqrt(d2.data, out=d2.data), out=d2.data) + # Refer Shi & Malik 2001, Equation 7, Page 891 vals, vectors = linalg.eigsh(d2 * (d - w) * d2, which='SM', k=min(100, m - 2)) - else: - stop = True - if not stop: - # Pick second smalles eigenvector. + # Pick second smallest eigenvector. # Refer Shi & Malik 2001, Section 3.2.3, Page 893 vals, vectors = np.real(vals), np.real(vectors) index2 = _ncut_cy.argmin2(vals) ev = _ncut.normalize(vectors[:, index2]) - threshold, mcut = get_min_ncut(ev, d, w, num_cuts) + cut_mask, mcut = get_min_ncut(ev, d, w, num_cuts) if (mcut < thresh): - cut_mask = ev > threshold # Sub divide and perform N-cut again # Refer Shi & Malik 2001, Section 3.2.5, Page 893 sub1, sub2 = partition_by_cut(cut_mask, rag) - _ncut_relabel(sub1, thresh, num_cuts, map_array) - _ncut_relabel(sub2, thresh, num_cuts, map_array) + _ncut_relabel(sub1, thresh, num_cuts) + _ncut_relabel(sub2, thresh, num_cuts) return # The N-cut wasn't small enough, or could not be computed. # The remaining graph is a region. # Assign `ncut label` by picking any label from the existing nodes, since # `labels` are unique, `new_label` is also unique. - node = rag.nodes()[0] - new_label = rag.node[node]['labels'][0] - for n, d in rag.nodes_iter(data=True): - for l in d['labels']: - map_array[l] = new_label + + _label_all(rag, 'ncut label') diff --git a/skimage/graph/rag.py b/skimage/graph/rag.py index f33936c2..1366a6d9 100644 --- a/skimage/graph/rag.py +++ b/skimage/graph/rag.py @@ -2,6 +2,7 @@ try: import networkx as nx except ImportError: msg = "Graph functions require networkx, which is not installed" + class nx: class Graph: def __init__(self, *args, **kwargs): @@ -119,7 +120,7 @@ def _add_edge_filter(values, graph): return 0 -def rag_mean_color(image, labels, connectivity=2, mode='dissimilarity', +def rag_mean_color(image, labels, connectivity=2, mode='distance', sigma=255.0): """Compute the Region Adjacency Graph using mean colors. @@ -142,15 +143,15 @@ def rag_mean_color(image, labels, connectivity=2, mode='dissimilarity', are considered adjacent. It can range from 1 to `labels.ndim`. Its behavior is the same as `connectivity` parameter in `scipy.ndimage.filters.generate_binary_structure`. - mode : str['similarity' | 'dissimilarity'] + mode : {'distance', 'similarity'}, optional The strategy to assign edge weights. - 'similarity' : The weight between two adjacent regions is the + 'distance' : The weight between two adjacent regions is the :math:`|c_1 - c_2|`, where :math:`c_1` and :math:`c_2` are the mean - colors of the two regions. It represents how different two regions - are. + colors of the two regions. It represents the Euclidian distance in + their average color. - 'dissimilarity' : The weight between two adjacent is + 'similarity' : The weight between two adjacent is :math:`e^{-d^2/sigma}` where :math:`d=|c_1 - c_2|`, where :math:`c_1` and :math:`c_2` are the mean colors of the two regions. It represents how similar two regions are. @@ -229,9 +230,9 @@ def rag_mean_color(image, labels, connectivity=2, mode='dissimilarity', diff = np.linalg.norm(diff) if mode == 'similarity': d['weight'] = math.e ** (-(diff ** 2) / sigma) - elif mode == 'dissimilarity': + elif mode == 'distance': d['weight'] = diff else: - raise ValueError("The mode '%s' is not recodnized" % mode) + raise ValueError("The mode '%s' is not recognised" % mode) return graph