Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normalized Cut on RAGs (WIP) #1080

Merged
merged 36 commits into from Aug 15, 2014
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7215936
First working copy
vighneshbirodkar Jul 22, 2014
ddf11bd
pep8 changes
vighneshbirodkar Jul 22, 2014
0971c11
Cleaned up rag.py
vighneshbirodkar Jul 22, 2014
997d4a3
cleaned up _ncut.py
vighneshbirodkar Jul 22, 2014
68b087c
Docstrings
vighneshbirodkar Jul 22, 2014
b05646e
Docstring of graph_cut.py
vighneshbirodkar Jul 22, 2014
1e39dfc
rectified threshold mistake
vighneshbirodkar Jul 22, 2014
273b9d0
docstrings to _ncut.py
vighneshbirodkar Jul 22, 2014
9c8b1ef
docstring to _ncut_cy.pys
vighneshbirodkar Jul 22, 2014
2017e35
Comments to graph_cut.py
vighneshbirodkar Jul 22, 2014
22b5f4a
Added example for ncut
vighneshbirodkar Jul 22, 2014
c915aef
Updated bento file
vighneshbirodkar Jul 22, 2014
d398b7f
used standard eignen solver
vighneshbirodkar Jul 23, 2014
fc9e8c4
doc string correction
vighneshbirodkar Jul 23, 2014
62f0909
Remove unused except caluses
vighneshbirodkar Jul 23, 2014
6cc0cf4
use map_array instead of modifying graph
vighneshbirodkar Jul 23, 2014
f261e51
rename method to cut_normalized
vighneshbirodkar Jul 23, 2014
7e2f33c
Refer sections of paper in comments
vighneshbirodkar Jul 23, 2014
d8c0b2e
Add unit test
vighneshbirodkar Jul 23, 2014
07cb79c
Cut cost is computed by Cython code
vighneshbirodkar Jul 31, 2014
452921d
API changes, comments and docstrings
vighneshbirodkar Aug 5, 2014
69fec94
rag copy in threshold cut
vighneshbirodkar Aug 5, 2014
3ad2b68
test skip
vighneshbirodkar Aug 5, 2014
35c9746
comment
vighneshbirodkar Aug 5, 2014
9371c4f
Fixed potential import error
vighneshbirodkar Aug 6, 2014
1fa7bce
skip error test
vighneshbirodkar Aug 6, 2014
6d0bf72
Docstring changes and typos
vighneshbirodkar Aug 8, 2014
3973b30
docstring changes and code movement
vighneshbirodkar Aug 9, 2014
ded138c
test case for in place
vighneshbirodkar Aug 9, 2014
03d3873
docstring of _label_all
vighneshbirodkar Aug 9, 2014
0c18562
corrected similarity
vighneshbirodkar Aug 9, 2014
4a231cf
Minor changes
vighneshbirodkar Aug 10, 2014
ab60edc
Fix Reference format
vighneshbirodkar Aug 10, 2014
6ef01cb
change ref format in example
vighneshbirodkar Aug 12, 2014
b617631
Update _ncut_cy.pyx
vighneshbirodkar Aug 14, 2014
afa345f
Update graph_cut.py
vighneshbirodkar Aug 14, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions bento.info
Expand Up @@ -154,6 +154,9 @@ Library:
Extension: skimage.feature._hessian_det_appx
Sources:
skimage/exposure/_hessian_det_appx.pyx
Extension: skimage.graph._ncut_cy
Sources:
skimage/graph/_ncut_cy.pyx

Executable: skivi
Module: skimage.scripts.skivi
Expand Down
32 changes: 32 additions & 0 deletions doc/examples/plot_ncut.py
@@ -0,0 +1,32 @@
"""
==============
Normalized Cut
==============

This example constructs a Region Adjacency Graph (RAG) and recursively performs
a Normalized Cut on it.

References
----------
.. [1] Shi, J.; Malik, J., "Normalized cuts and image segmentation",
Pattern Analysis and Machine Intelligence,
IEEE Transactions on , vol.22, no.8, pp.888,905, Aug 2000
"""
from skimage import graph, data, io, segmentation, color
from matplotlib import pyplot as plt


img = data.coffee()

labels1 = segmentation.slic(img, compactness=30, n_segments=400)
out1 = color.label2rgb(labels1, img, kind='avg')

g = graph.rag_mean_color(img, labels1, mode='similarity')
labels2 = graph.cut_normalized(labels1, g)
out2 = color.label2rgb(labels2, img, kind='avg')

plt.figure()
io.imshow(out1)
plt.figure()
io.imshow(out2)
io.show()
5 changes: 4 additions & 1 deletion skimage/graph/__init__.py
@@ -1,7 +1,8 @@
from .spath import shortest_path
from .mcp import MCP, MCP_Geometric, MCP_Connect, MCP_Flexible, route_through_array
from .rag import rag_mean_color, RAG
from .graph_cut import cut_threshold
from .graph_cut import cut_threshold, cut_normalized
ncut = cut_normalized

__all__ = ['shortest_path',
'MCP',
Expand All @@ -11,4 +12,6 @@
'route_through_array',
'rag_mean_color',
'cut_threshold',
'cut_normalized',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think "ncut" is common enough in the literature that we actually want an alias for it: ncut = cut_normalized. You can add this to the source file and update this list. (And above.)

'ncut',
'RAG']
85 changes: 85 additions & 0 deletions skimage/graph/_ncut.py
@@ -0,0 +1,85 @@
try:
import networkx as nx
except ImportError:
import warnings
warnings.warn('RAGs require networkx')
import numpy as np
from scipy import sparse
from . import _ncut_cy


def DW_matrices(graph):
"""Returns the diagonal and weight matrices of a graph.

Parameters
----------
graph : RAG
A Region Adjacency Graph.

Returns
-------
D : csc_matrix
The diagonal matrix of the graph. ``D[i, i]`` is the sum of weights of
all edges incident on `i`. All other enteries are `0`.
W : csc_matrix
The weight matrix of the graph. ``W[i, j]`` is the weight of the edge
joining `i` to `j`.
"""
# sparse.eighsh is most efficient with CSC-formatted input
W = nx.to_scipy_sparse_matrix(graph, format='csc')
entries = W.sum(axis=0)
D = sparse.dia_matrix((entries, 0), shape=W.shape).tocsc()
return D, W


def ncut_cost(cut, D, W):
"""Returns the N-cut cost of a bi-partition of a graph.

Parameters
----------
cut : ndarray
The mask for the nodes in the graph. Nodes corressponding to a `True`
value are in one set.
D : csc_matrix
The diagonal matrix of the graph.
W : csc_matrix
The weight matrix of the graph.

Returns
-------
cost : float
The cost of performing the N-cut.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a "References" section and point to the page, line, and equation number in the paper?


References
----------
.. [1] Normalized Cuts and Image Segmentation, Jianbo Shi and
Jitendra Malik, IEEE Transactions on Pattern Analysis and Machine
Intelligence, Page 889, Equation 2.
"""
cut = np.array(cut)
cut_cost = _ncut_cy.cut_cost(cut, W)

# D has elements only along the diagonal, one per node, so we can directly
# index the data attribute with cut.
assoc_a = D.data[cut].sum()
assoc_b = D.data[~cut].sum()

return (cut_cost / assoc_a) + (cut_cost / assoc_b)


def normalize(a):
"""Normalize values in an array between `0` and `1`.

Parameters
----------
a : ndarray
The array to be normalized.

Returns
-------
out : ndarray
The normalized array.
"""
mi = a.min()
mx = a.max()
return (a - mi) / (mx - mi)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a constant array valid input? Then this would divide by zero.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ncut code never produced such an array. Should I added a check for that ? And ideally what would it be mapped to, an array of 0s or 1s ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I'm happy to let this be an error. It would have to be a pretty pathological image for it to produce such a vector.

78 changes: 78 additions & 0 deletions skimage/graph/_ncut_cy.pyx
@@ -0,0 +1,78 @@
# cython: cdivision=True
# cython: boundscheck=False
# cython: nonecheck=False
# cython: wraparound=False
cimport numpy as cnp
import numpy as np


def argmin2(cnp.double_t[:] array):
"""Return the index of the 2nd smallest value in an array.

Parameters
----------
a : array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parameter has different name in signature above?

The array to process.

Returns
-------
min_idx2 : int
The index of the second smallest value.
"""
cdef cnp.float64_t min1 = np.inf
cdef cnp.float64_t min2 = np.inf
cdef Py_ssize_t min_idx1 = 0
cdef Py_ssize_t min_idx2 = 0
cdef Py_ssize_t i = 0
cdef Py_ssize_t n = array.shape[0]

for i in range(n):
x = array[i]
if x < min1:
min2 = min1
min_idx2 = min_idx1
min1 = x
min_idx1 = i
elif x > min1 and x < min2:
min2 = x
min_idx2 = i
i += 1

return min_idx2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this reads way better. =D



def cut_cost(cut, W):
"""Return the total weight of crossing edges in a bi-partition.

Parameters
----------
cut : array
A array of booleans. Elements set to `True` belong to one
set.
W : array
The weight matrix of the graph.

Returns
-------
cost : float
The total weight of crossing edges.
"""
cdef cnp.ndarray[cnp.uint8_t, cast = True] cut_mask = np.array(cut)
cdef Py_ssize_t num_rows, num_cols
cdef cnp.int32_t row, col
cdef cnp.int32_t[:] indices = W.indices
cdef cnp.int32_t[:] indptr = W.indptr
cdef cnp.double_t[:] data = W.data.astype(np.double)
cdef cnp.int32_t row_index
cdef cnp.double_t cost = 0

num_rows = W.shape[0]
num_cols = W.shape[1]

for col in range(num_cols):
for row_index in range(indptr[col], indptr[col + 1]):
row = indices[row_index]
if cut_mask[row] != cut_mask[col]:
cost += data[row_index]

return cost * 0.5