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

Fix dynamic max trials of RANSAC #7065

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

hayatoikoma
Copy link
Contributor

@hayatoikoma hayatoikoma commented Jul 14, 2023

Description

Due to numerical precision, denom sometimes becomes exactly 1.0 so that np.log(denom) becomes 0.0. As the numerator is always negative, the function returns -np.inf. This PR fixes this issue.

Release note

Summarize the introduced changes in the code block below in one or a few sentences. The
summary will be included in the next release notes automatically:

Fix numerical precision error in RANSAC. In some cases, RANSAC was stopping at the first iteration.

assert_equal(_dynamic_max_trials(1, 100, 5, 1), 360436504051)
# e = 0%, min_samples = 10
assert_equal(_dynamic_max_trials(1, 100, 10, 0), 0)
assert_equal(_dynamic_max_trials(1, 100, 10, 1), 162326183972299328)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

On main, it returns -np.inf.

Copy link
Member

Choose a reason for hiding this comment

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

Can confirm. But why modify the existing test and not add another one precisely for the error case including a comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With my first initial implementation, I thought that min_samples = 5 would be redundant because the result for min_samples=5 changes. But with your suggested change, the result doesn't change so that it makes sense to keep it. Just added it back with my new commit.

@hayatoikoma
Copy link
Contributor Author

The CI seems to be failing due to numerical stability between different binaries? As I don't know the difference of the CIs, I am leaving it as it is.

@mkcor mkcor added the 🩹 type: Bug fix Fixes unexpected or incorrect behavior label Oct 16, 2023
Copy link
Member

@lagru lagru left a comment

Choose a reason for hiding this comment

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

Thanks. The current test failures look unrelated to this PR (imageio, tracked in imageio/imageio#1044). So ignore them for now.

I've left a few minor suggestions, otherwise looks good.

skimage/measure/fit.py Outdated Show resolved Hide resolved
assert_equal(_dynamic_max_trials(1, 100, 5, 1), 360436504051)
# e = 0%, min_samples = 10
assert_equal(_dynamic_max_trials(1, 100, 10, 0), 0)
assert_equal(_dynamic_max_trials(1, 100, 10, 1), 162326183972299328)
Copy link
Member

Choose a reason for hiding this comment

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

Can confirm. But why modify the existing test and not add another one precisely for the error case including a comment?

@hayatoikoma
Copy link
Contributor Author

Thanks for the review! I just pushed the changes based on the suggestion.

lagru
lagru previously approved these changes Oct 17, 2023
Copy link
Member

@lagru lagru left a comment

Choose a reason for hiding this comment

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

Looks good, thanks. Feel welcome to tweak my suggested comment further.

@@ -668,7 +668,8 @@ def _dynamic_max_trials(n_inliers, n_samples, min_samples, probability):
return np.inf
inlier_ratio = n_inliers / n_samples
nom = max(_EPSILON, 1 - probability)
denom = max(_EPSILON, 1 - inlier_ratio ** min_samples - _EPSILON)
denom = max(_EPSILON, 1 - inlier_ratio ** min_samples)
denom = min(denom, 1 - _EPSILON)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
denom = min(denom, 1 - _EPSILON)
# Avoid log(1) below turning into -inf
denom = min(denom, 1 - _EPSILON)

@lagru lagru dismissed their stale review October 17, 2023 13:44

failures in CI

Also move it into its own test at is isn't part of the hand-calculated
values in Multiple View Geometry in Computer Vision in
test_ransac_dynamic_max_trials.
@lagru
Copy link
Member

lagru commented Oct 17, 2023

@hayatoikoma, I took the liberty to push 8d5aa4a. Please feel welcome to disagree or tweak further. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very nice! Thank you for improving the test and fixing the CI!

@hayatoikoma
Copy link
Contributor Author

LGTM!

Copy link
Member

@lagru lagru left a comment

Choose a reason for hiding this comment

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

Agreed, looks ready to go. :)

@hayatoikoma
Copy link
Contributor Author

I just found that the function was a transplant from scikit-learn, and scikit-learn is handling the edge case in a slightly different way. I'm sure that it doesn't matter, but just FYI.

#6046

https://github.com/scikit-learn/scikit-learn/blob/2a2772a87b6c772dc3b8292bcffb990ce27515a8/sklearn/linear_model/_ransac.py#L23-L54

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🩹 type: Bug fix Fixes unexpected or incorrect behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants