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
Remove bug in ARE in evaluate.py #89
Conversation
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.
@JohnnyTeutonic pretty good but still some work to do here! =)
gala/evaluate.py
Outdated
@@ -927,10 +927,11 @@ def rand_by_threshold(ucm, gt, npoints=None): | |||
return np.concatenate((ts[np.newaxis, :], result), axis=0) | |||
|
|||
def adapted_rand_error(seg, gt, all_stats=False): | |||
from gala import evaluate as ev |
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.
Erm...
gala/evaluate.py
Outdated
@@ -942,6 +943,7 @@ def adapted_rand_error(seg, gt, all_stats=False): | |||
all_stats : boolean, optional | |||
whether to also return precision and recall as a 3-tuple with rand_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.
Remove this extra newline
gala/evaluate.py
Outdated
@@ -952,40 +954,43 @@ def adapted_rand_error(seg, gt, all_stats=False): | |||
rec : float, optional | |||
The adapted Rand recall. (Only returned when `all_stats` is ``True``.) | |||
|
|||
|
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 one too
gala/evaluate.py
Outdated
# This is the contingency table obtained from segA and segB, we obtain | ||
# the marginal probabilities from the table. | ||
p_ij = ev.contingency_table(segA, segB, norm=False) | ||
contingency_table = p_ij.A |
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.
WHOA. Do not do this. This instantiates a potentially enormous array. The contingency table should be usable in sparse format. .A
is used to make numpy arrays out of e.g. the marginals, which are much smaller (n + m instead of n * m)
gala/evaluate.py
Outdated
|
||
p_ij = sparse.csr_matrix((ones_data, (segA[:], segB[:])), shape=(n_labels_A, n_labels_B)) | ||
# Sum of the joint distribution squared | ||
sum_p_ij = np.sum(contingency_table**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.
sum_p_ij = np.sum(p_ij.data ** 2)
See Elegant SciPy, Nunez-Iglesias et al, Chapter 5. =P
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.
actually, check the comment about sum_a
: sum_p_ij = p_ij.data @ p_ij.data
.
gala/evaluate.py
Outdated
precision = sumAB / sumB | ||
recall = sumAB / sumA | ||
precision = (sum_p_ij - n )/ (sum_a - n) | ||
recall = (sum_p_ij -n )/ (sum_b - 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.
PEP8 here also
gala/evaluate.py
Outdated
|
||
fScore = 2.0 * precision * recall / (precision + recall) | ||
are = 1.0 - fScore | ||
|
||
are = abs(1.0 - fScore) |
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.
Just write 1 - f_score
here — the 1.0
is a legacy construct from Python 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.
Also, you shouldn't need abs here...?
gala/evaluate.py
Outdated
precision = sumAB / sumB | ||
recall = sumAB / sumA | ||
precision = (sum_p_ij - n )/ (sum_a - n) | ||
recall = (sum_p_ij -n )/ (sum_b - n) | ||
|
||
fScore = 2.0 * precision * recall / (precision + recall) |
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.
Not your responsibility, but you might as well fix fScore
-> f_score
while you're here. =)
tests/test_evaluate.py
Outdated
def test_are(): | ||
seg = np.array([[0,1], [1,0]]) | ||
gt = np.array([[1,2],[0,1]]) | ||
assert_almost_equal(abs(ev.adapted_rand_error(seg,gt)),0.3333333) |
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 is there an abs
here?
tests/test_evaluate.py
Outdated
seg = np.array([[0,1], [1,0]]) | ||
gt = np.array([[1,2],[0,1]]) | ||
assert_almost_equal(abs(ev.adapted_rand_error(seg,gt)),0.3333333) | ||
assert seg.shape == gt.shape |
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 always true by construction, so you can remove it.
Okay, I included all your changes and also removed the unnecessary ravel functional calls (segA = np.ravel(gt), segB = np.ravel(seg)) at the beginning of the function, as I realised that the contingency table already flattens the input arrays anyway. So that removes some more redundant code. |
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.
@JohnnyTeutonic I've requested one more change. It's minor, I promise! =)
tests/test_evaluate.py
Outdated
|
||
def test_are(): | ||
seg = np.array([[0, 1], [1, 0]]) | ||
gt = np.array([[1, 2],[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.
@JohnnyTeutonic part of the reason this whole issue arose was the way 0 was being treated as background. We've decided to not do this, but, in case we eventually do, it would be best if this test didn't use the 0 label. So can you please relabel 0s to 3 in both volumes?
As per discussion at cremi/cremi_python#3 (comment) about errors with the normalisation of the Adapted Rand Errror, I have incorporated the changes to the ARE, adapted from the fixed code found in the above discussion, which is seen in the link: https://gist.github.com/thouis/63888c375cbeb2f702e94e2e82eebee8.
The main change to the code is the removal of the division by 'n' , which previously had included division by both zero and non-zero pixels, which occurred when calculating the sum of the pixels in segments A and B. So now this code reflects only division by non-zero pixels, which is what should have been reflected in the reference implementation.