-
-
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
Ellipsis fitting: Check the condition number before computiing eigen vectors #3103
base: main
Are you sure you want to change the base?
Ellipsis fitting: Check the condition number before computiing eigen vectors #3103
Conversation
Hello @hmaarrfk! Thanks for updating the PR.
Comment last updated on September 20, 2018 at 03:00 Hours UTC |
9fd7a12
to
4d40a5d
Compare
Codecov Report
@@ Coverage Diff @@
## master #3103 +/- ##
========================================
- Coverage 86.79% 86.7% -0.1%
========================================
Files 341 341
Lines 27444 27429 -15
========================================
- Hits 23820 23782 -38
- Misses 3624 3647 +23
Continue to review full report at Codecov.
|
skimage/measure/fit.py
Outdated
@@ -434,6 +434,11 @@ def estimate(self, data): | |||
except np.linalg.LinAlgError: # LinAlgError: Singular matrix | |||
return False | |||
|
|||
# For some reason the above doesn't catch the error on linux 32 bit | |||
# https://stackoverflow.com/questions/13249108/efficient-pythonic-check-for-singular-matrix | |||
if np.linalg.cond(M) > 1 / np.finfo(np.double).eps: |
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 check can be done before the inversion is attempted? It simplifies the logic a bit.
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'm not sure. in fact, when I computed cond(M)
in the debugger with import pdb; pdb.set_trace()
, the condition number was already Inf
. I don't really want to cause other overflow errors or "old numpy specific errors"
From the docs
eps | (float) The smallest representable positive number such that 1.0 + eps != 1.0. Type of eps is an appropriate floating point type.
They don't seem to have a eps^-1. Thought I could do
eps**-1
if you like.
It seems like the test suite already fails, but it may be worth inserting an explicit test for this, potentially one that also fails on 64-bit. |
Should this be a bug report to numpy too? |
@matthew-brett I've raised this with the NumPy folks; they think it's not NumPy's responsibility. Overall, it is a bad idea to use inverse if you can use solve. Could we rewrite the solution here to do solves instead? |
Just for you two, I actually stepped through to figure out what was happening.
# M*|a b c >=l|a b c >. Find eigenvalues and eigenvectors
# from this equation [eqn. 28]
eig_vals, eig_vecs = np.linalg.eig(M)
# eigenvector must meet constraint 4ac - b^2 to be valid.
cond = 4 * np.multiply(eig_vecs[0, :], eig_vecs[2, :]) \
- np.power(eig_vecs[1, :], 2)
a1 = eig_vecs[:, (cond > 0)]
# seeks for empty matrix
if 0 in a1.shape or len(a1.ravel()) != 3:
return False On 64 bit:
On 32 bit
It just happened that on 64 bit the error didn't show up. But I don't think that the Therefore, the tests in both 64 bit and 32 bit are caught by my added logic. |
Technically, since the matrix is so small, I think we can simply check that if one Anyway, I think checking the condition number is more "mathematical" |
@stefanv. I probably agree that doing something like finding tge best eigen value is probably verbose. From ‘numpy.linalg.solve’
We would need to check the condition number of the matrix anyway otherwise risk having an other linalg error |
Finally @stefanv. I quickly skimmed the paper and the code seems to be following the formalism the paper is using. I suggest that we leave the reformulation to somebody that wants to derive it. (It May be a paper for itself) |
e2b7248
to
d8c098c
Compare
So, should we merge this PR? |
I think so! I can rebase if you want. |
d8c098c
to
e81aadb
Compare
@hmaarrfk I can't quite tell from the conversation whether you have a test case that will fail on 64 bit? It would be nice for this to be tested. |
Sorry, I guess I coudln't think of one. I'll try to bring out my pen and paper this weekend. Remind me. |
5381ec2
to
3eee483
Compare
You can go ahead and force push to remove my "Reverting" of the fix once travis shows that the tests fail. I'm not really a fan of using Also, you can precompute the inv of |
Failed on Appveyor: https://ci.appveyor.com/project/scikit-image/scikit-image/build/1.0.1014/job/qqf67dhmccr8f0so#L8458 |
3eee483
to
34395c3
Compare
Python 3.7 finally failed. I guess adding all those tests was worth it. |
12666fc
to
9b5aae0
Compare
@jni Here is what recreates the issue: from skimage.measure import EllipseModel
import numpy as np
model = EllipseModel()
print(model.estimate(np.array([[50, 80], [51, 81], [52, 80.00000000001]])))
print(model.estimate(np.array([[50, 80], [51, 81], [52, 80.0001000001]])))
print(model.estimate(np.array([[50, 80], [51, 81], [52, 80.000000001]])))
print(model.estimate(np.array([[50, 80], [51, 81], [52, 80.00000001]])))
print(model.estimate(np.array([[50, 80], [51, 81], [52, 80.0000001]])))
print(model.estimate(np.array([[50, 80], [51, 81], [52, 80.000001]])))
print(model.estimate(np.array([[50, 80], [51, 81], [52, 80.00001]])))
I thought you could simply check the condition number, but it fails the doctest, which should clearly pass.... |
part of me thinks we should chek the number of input points. The problem of fitting an ellipse is ilposed with less than 5 points |
skimage/measure/tests/test_fit.py
Outdated
# 64 bit arch | ||
assert not model.estimate(np.array([[50, 80], [51, 81], [52, 80.00000000001]])) | ||
# Unfortunately, it seems to depend on the version of python that is used | ||
# Therefore, test for many cases |
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.
@hmaarrfk based on your comment, shouldn't this test be:
assert not all(model.estimate(np.array([[50, 80], [51, 81], [52, 80 + 10**(-n)]]))
for n in range(4, 12))
?
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, refactored as per your suggestion. problem remains :/
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.
Which problem? Do they all pass for some Python versions?
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.
Well my fix causes the doctest to fail. But the doctest seems to make sense. https://travis-ci.org/scikit-image/scikit-image/jobs/432304643#L2823
I'm glad to make the test xfail for code self-documentation.
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.
OOOOoooooOOOOhhhh. Very interesting. Hmm, I think ellipse-fitting to a line should work, actually. =\
9b5aae0
to
6ce1e2e
Compare
…ipsis fitting Explicitely check the condition number of the matrix before computing the eigen vectors
assert not model.estimate(np.array([[50, 80], [51, 81], [52, 80]])) | ||
assert not model.estimate(np.array([[50, 80], | ||
[51, 81], | ||
[52, 80 + epsilon]])) |
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.
according to the comment, what is the epsilon number (it is not clear from the review scope)
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, quite often, epsilon is a small number in math. I could add a comment, but this wasn't the right approach anyway, and gave the same results as before.
Under certain conditions, the result of a matrix we have to invert becomes ill conditioneed. The 32 bit solver and 64 bit solver converge on different answers.
We first check if that matrix is ill conditioned before we move forward with the computation.
See #3091
Checklist
[It's fine to submit PRs which are a work in progress! But before they are merged, all PRs should provide:]
./doc/examples
(new features only)[For detailed information on these and other aspects see scikit-image contribution guidelines]
References
[If this is a bug-fix or enhancement, it closes issue # ]
[If this is a new feature, it implements the following paper: ]
For reviewers
(Don't remove the checklist below.)
later.
__init__.py
.doc/release/release_dev.rst
.