-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
Implemented Kadir and Brady's Saliency detection #2299
base: main
Are you sure you want to change the base?
Conversation
Current coverage is 90.68% (diff: 100%)@@ master #2299 diff @@
==========================================
Files 304 307 +3
Lines 21539 21684 +145
Methods 0 0
Messages 0 0
Branches 1852 1865 +13
==========================================
+ Hits 19520 19665 +145
Misses 1665 1665
Partials 354 354
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @parulsethi!
I've left a few comments about general style and syntax.
For tests, have a look at the skimage/feature/tests
directory for examples. You should add another file, test_kadir_brady.py
, that compares the output of the function with known output for a given input.
For the gallery, see in doc/examples/feature_dection/
for some templates that you can copy. Simply add a file next to those, plot_kadir_brady.py
, that illustrates the detector.
Finally, as a general comment, some of the functions are a bit long — you should try to keep the function length shorter than 30-50 lines. If it's longer, see whether you can pull some loops into their own functions.
Hope this helps! =)
skimage/feature/_kb.py
Outdated
from .._shared.utils import assert_nD | ||
|
||
|
||
def kb(image, min_scale=10, max_scale=25, saliency_threshold=0.6, clustering_threshold=7): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest you rename this saliency_kadir_brady
. kb
is definitely not informative enough!
skimage/feature/_kb.py
Outdated
|
||
|
||
def kb(image, min_scale=10, max_scale=25, saliency_threshold=0.6, clustering_threshold=7): | ||
"""Finds salient regions in the given grayscale image.For each point x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a single sentence followed by a blank line, and should be in present imperative ("Finds salient regions" -> "Find salient regions"). Please review https://www.python.org/dev/peps/pep-0257/ and apply here.
skimage/feature/_kb.py
Outdated
Features with saliency score satisfying threshold will be considered. | ||
clustering_threshold : int, optional | ||
Variance among the regions should be smaller than this threshold | ||
for sufficient clustering. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work on the numpy style parameter list! =)
skimage/feature/_kb.py
Outdated
|
||
Returns | ||
------- | ||
A : (n, 3) ndarray |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by convention we use "array of float, shape (n, 3)"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jni I'm sorry, where do we use this notation? I bet that out : (K, 2) ndarray of float
is the most oftenly used version all over the package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@soupault you are absolutely right! Oops. Dunno where I got that other idea... =\
@parulsethi sorry about this mistake! But, at least, @soupault agree that the N should be capitalised. =)
skimage/feature/_kb.py
Outdated
assert_nD(image, 2) | ||
|
||
# scales for keypoints | ||
scales = np.array([_ for _ in range(min_scale, max_scale, 2)]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Three overlapping critiques here:
- Only use
_
for values that are discarded. In this case, the values are collected in the array, so you would use e.g.np.array([scale for scale in range(min_scale, max_scale, 2)])
- But actually, you're just converting to a list, so instead, prefer
np.array(list(range(min_scale, max_scale, 2)))
- But, actually, you're just converting to an array, so instead use
np.arange(min_scale, max_scale, 2)
.
=)
skimage/feature/_kb.py
Outdated
if s_count >= 1: | ||
# first derivative in entropy space to calculate weights | ||
dif = abs(h - previous_h[:, i]) | ||
factor = scales[s_count]**2/(2*scales[s_count]-1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Read PEP8 for suggestions about spacing around mathematical operators
skimage/feature/_kb.py
Outdated
|
||
|
||
def prune(candidate_regions, saliency_threshold, v_th, K=7): | ||
""" It selects highly salient points that have local support |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment about docstrings
skimage/feature/_kb.py
Outdated
r, c = np.nonzero(mask) | ||
nPix = len(r) | ||
|
||
nScales = len(scales) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use camelCase, use under_scores
skimage/feature/_kb.py
Outdated
# compute the histogram of intensity values in this region | ||
patch = image[min_r:max_r, min_c:max_c] | ||
h = np.histogram(patch, bins=intensity_edges)[0] | ||
h = np.array([_/sum(h) for _ in h]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
h /= np.sum(h)
skimage/feature/_kb.py
Outdated
return np.array([gamma, scale, row, column]) | ||
|
||
|
||
def prune(candidate_regions, saliency_threshold, v_th, K=7): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use uppercase arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or value names
Hey @jni, Made all the changes requested above, the code is bit changed for using Please suggest if any further improvements are required. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@parulsethi this is getting much closer! But I think it will benefit from another round of edits.
I also think that, with a bit of thought and care, it could be converted to support nD images, instead of just 2D.
For now, I would really appreciate it if you could address the comments I've left!
from ..util import img_as_ubyte, view_as_windows | ||
from .._shared.utils import assert_nD | ||
|
||
def saliency_kadir_brady(image, min_scale=5, max_scale=13, saliency_threshold=0.6, clustering_threshold=2): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@parulsethi PEP8 requires all lines to be wrapped at 80 characters wide.
What editor are you using? Most modern editors will automatically wrap lines for you. I recommend PyCharm.
for row in patches: | ||
for patch in row: | ||
h = np.histogram(patch, bins=intensity_edges)[0] | ||
h = h / np.sum(h) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you can just use np.histogram(..., normed=True)
for s_idx, scale in enumerate(scales): | ||
# shape for windows according to current scale | ||
radius = scale + 1 | ||
window_shape = (radius, radius) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is better described as "sidelen" or "diameter" than "radius"
nr, nc = image.shape | ||
# find pixels that we are going to examine | ||
mask = np.ones((nr - max(scales), nc - max(scales))) | ||
r, c = np.nonzero(mask) + max(scales) / 2 + 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are trying to add a tuple (np.nonzero(.)
) to a float (max(scales) / 2
), which is an error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
r and c are assigned here the 2 arrays inside the tuple, so it's actually array+float
In [96]: np.nonzero(mask)
Out[96]: (array([0, 0, 1, 1]), array([0, 1, 0, 1]))
In [97]: r, c = np.nonzero(mask)
In [98]: r
Out[98]: array([0, 0, 1, 1])
In [99]: c
Out[99]: array([0, 1, 0, 1])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@parulsethi Python evaluates the RHS entirely before assignment, so it first will try to add a tuple to a float, and then unpack the arrays within the tuple:
In [1]: import numpy as np
In [2]: mask = np.random.rand(5, 5) > 0.5
In [3]: r, c = np.nonzero(mask) + 1.5
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-036d7e86f56f> in <module>()
----> 1 r, c = np.nonzero(mask) + 1.5
TypeError: can only concatenate tuple (not "float") to tuple
# iterate through scales | ||
for s_idx, scale in enumerate(scales): | ||
# shape for windows according to current scale | ||
radius = scale + 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the +1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to make no. of iterating windows equal to n_pix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or to just have those windows whose center pixel equals the pixels which are considered from n_pix (or mask)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, can you elaborate a bit more here? I still don't understand why we don't use the scale as the radius — it seems to be some form of convention, but it doesn't seem justified to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In [68]: scales = [5,3]
...: image= np.random.rand(10, 10)
...: nr, nc = image.shape
...:
...: mask = np.ones((nr - max(scales), nc - max(scales)))
...: r, c = np.nonzero(mask)
...: n_pix = len(r)
...:
In [69]: n_pix
Out[69]: 25
let current_scale = scales[0]
and if i set diameter(thanks for pointing radius being misleading) without doing +1
In [73]: patches = util.view_as_windows(image, current_scale)
In [74]: patches.shape
Out[74]: (6, 6, 5, 5)
which basically gives no. of patches => 6*6 = 36
but then with +1
In [75]: patches = util.view_as_windows(image, current_scale+1)
In [76]: patches.shape
Out[76]: (5, 5, 6, 6)
now, no. of patches => 5*5 = 25 which is equal to n_pix computed above.
This is basically because the no. of windows are image.shape - scale+1, so i can either do diameter = scale+1 or do mask = np.ones((nr-max(scales)+1, nc-max(scales)+1))
above which would then give n_pix=36 if we take diameter same as scale.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's n_pix which should be calculated with (nr-max(scales)+1, nc-max(scales)+1)
, and diameter should be left same as scale.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@parulsethi yes, this last statement makes the most sense to me. =)
h = np.histogram(patch, bins=intensity_edges)[0] | ||
h = h / np.sum(h) | ||
|
||
# index of histogram values greater than zero |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In comments, don't simply state what the code is doing (that's what the code is for), but why you're doing it. In this case, "find histogram values greater than zero to prevent NaNs when computing 0log(0) during entropy computation"
t_r : (N,) ndarray of float | ||
Row index of selected regions. | ||
t_c : (N,) ndarray of float | ||
Cloumn index of selected regions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Column
Parameters | ||
---------- | ||
candidate_regions : (4, N) ndarray of float | ||
A 2d array with each column representing gamma,scale,y,x. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use "row, column" or "r, c" instead of "y, x".
column = np.append(column, center[0]) | ||
row = np.append(row, center[1]) | ||
scale = np.append(scale, center[2]) | ||
gamma = np.append(gamma, t_gamma[index]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is extremely inefficient... Use lists to append, and convert to array at the end!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for this!
cluster = np.zeros((3, k + 1)) | ||
|
||
# pruning process | ||
for index in range(n): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain what you're doing here? It seems to me that you should be able to simplify the logic quite a bit. For example, the code block for the first region (under the else
) is the same as the code block for a sufficiently-far region. I'm sure you'd be able to condense both code paths into a single clause.
@parulsethi What's the status of this PR for you please? Do you still want to go through @jni's comments? |
Sorry for the delay, I made most of the changes in the review above, will push them here, but was stuck at one of them - replied in the corresponding review comment. |
Thanks @parulsethi! Feel free to push the modifications you made so far. Could you also tick the box (if not done yet) on the right of this page. This is to allow to push on your branch as well. That could be useful for us. thank you. |
Description
This PR implements Kadir and Brady's Saliency detection algorithm which calculates the saliency score by measuring the entropy of the region's descriptor(to find locally complex regions) and weigh them by the difference over multiple scales to select features that are globally discriminative.
![screen shot 2016-09-09 at 12 07 44 am](https://cloud.githubusercontent.com/assets/11822050/18388850/d8e08e16-76c0-11e6-92e7-6d2d52173e51.png)
Result over astronaut():
As this is my first contribution to not only scikit-image but open-source too, so please suggest the changes that are required in order to merge this, if you are interested in adding this feature.
I haven't yet prepared the doc example as their are some parameters in this algorithm over which i need your suggestion that whether to leave them as optional or set it to the optimized one. These are the saliency and clustering thresholds.
Checklist
./doc/examples
(new features only)References
Kadir, Timor, and Michael Brady. "Saliency, scale and image description." International Journal of Computer Vision 45.2 (2001): 83-105.