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

AstroNet mesh frontend metrics #295

Merged
merged 38 commits into from
Nov 4, 2021
Merged

Conversation

travisdriver
Copy link
Collaborator

@travisdriver travisdriver commented Sep 14, 2021

This PR adds a front-end evaluation based on a provided ground truth surface mesh of the scene. High-fidelity ground truth shape models are available for small bodies explored by past missions, allowing for highly accurate evaluation of the correspondences computes by the front-end.

Through testing, the current Sampson distance (SD) method seems to consistently over-estimate the precision of the front end, at least for the AstroNet examples I have run. For example, on a 14-image sequence from Dawn @ 4 Vesta, the Sampson distance method estimated an overall precision of .9998 while the mesh-based method computed a precision of .9388.

Moreover, I ran GTSfM using the ground truth information to verify correspondences using both the SD and mesh-based methods as a sanity check. When correspondences were verfied using the ground truth mesh, the final reconstruction was perfect. However, the result using the SD method was far from perfect.

I still think the Sampson distance is a useful metric for when a ground truth mesh is not present, but I think the mesh-based front-end evaluation provides a more accurate estimate of the precision

Mesh-based method

mean_inlier_ratio_wrt_gt_model: 0.9998

Metrics for pair (0, 8)

(0, 8)
"rotation_angular_error": 15.69,
"translation_angular_error": 8.21,
"num_inliers_gt_model": 376,
 "inlier_ratio_gt_model": 0.82,
"inlier_avg_reproj_error_gt_model": 1.86,
 "outlier_avg_reproj_error_gt_model": 7.34,
"inlier_ratio_est_model": 0.78,
 "num_inliers_est_model": 459

0_8

Final reconstruction with ground truth verified correspondences

Screenshot from 2021-10-09 11-00-06

Sampson distance method

mean_inlier_ratio_wrt_gt_model: 0.9388

Metrics for pair (0, 8)

(0, 8)
"rotation_angular_error": 15.69,
"translation_angular_error": 8.21,
"num_inliers_gt_model": 459,
"inlier_ratio_gt_model": 1.0,
"inlier_avg_reproj_error_gt_model": 0.63,
"outlier_avg_reproj_error_gt_model": NaN,
"inlier_ratio_est_model": 0.78,
"num_inliers_est_model": 459

0_8

Final reconstruction with ground truth verified correspondences

Screenshot from 2021-10-09 10-44-36

@travisdriver travisdriver linked an issue Sep 18, 2021 that may be closed by this pull request
2 tasks
@travisdriver travisdriver mentioned this pull request Oct 13, 2021
gtsfm/utils/metrics.py Outdated Show resolved Hide resolved
gtsfm/utils/metrics.py Outdated Show resolved Hide resolved
gtsfm/utils/metrics.py Outdated Show resolved Hide resolved
Copy link
Contributor

@ayushbaid ayushbaid 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 overall. My main concern is that why we compute reprojection errors only on points which are inliers from epipolar constraint.

gtsfm/loader/astronet_loader.py Show resolved Hide resolved
gtsfm/loader/astronet_loader.py Show resolved Hide resolved

with Client(cluster) as client, performance_report(filename="dask-report.html"):
# Scatter surface mesh across all nodes to preserve computation time and memory.
gt_scene_trimesh_future = client.scatter(self.loader.gt_scene_trimesh, broadcast=True)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is interesting. Do you have any experiments/rough approximations on the savings obtained by scatter?

If you recommend, we can use this scatter at other places too (like NN models)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To be honest, the main reason I did it is because Dask told me to, but I can definitely say it saves a significant amount of computation time (although I don't have any exact numbers).

The documentation is a little spotty, but from my understanding, it preallocates memory for the object on each worker and then passes around the pointer to the worker threads.

It definitely may be useful for other large objects in the pipeline.

gtsfm/scene_optimizer.py Outdated Show resolved Hide resolved
gtsfm/scene_optimizer.py Outdated Show resolved Hide resolved
Comment on lines +173 to +176
num_inliers_gt_model = np.count_nonzero(v_corr_idxs_inlier_mask_gt)
inlier_ratio_gt_model = (
np.count_nonzero(v_corr_idxs_inlier_mask_gt) / v_corr_idxs.shape[0] if len(v_corr_idxs) > 0 else 0.0
)
Copy link
Contributor

Choose a reason for hiding this comment

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

this should just depend on v_corr_idxs_inlier_mask_gt not being None

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I'm getting a lot of mypy errors for a lot of variables because many of them are labeled as Optional. I think this should be part of a larger effort where we reassess whether certain variables are, in fact, optional.

gtsfm/two_view_estimator.py Show resolved Hide resolved
gtsfm/utils/metrics.py Show resolved Hide resolved
gtsfm/utils/metrics.py Show resolved Hide resolved
return is_inlier, reproj_err


def compute_keypoint_intersections(
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I live putting it in the new file. This is a function which can potentially be used in other components too (in the future).

@travisdriver
Copy link
Collaborator Author

Looks good overall. My main concern is that why we compute reprojection errors only on points which are inliers from epipolar constraint.

The reprojection errors are computed for all keypoints, not just inliers.

@travisdriver travisdriver merged commit ef9e1e6 into master Nov 4, 2021
if report.reproj_error_gt_model is not None and report.v_corr_idxs_inlier_mask_gt is not None
else None,
"outlier_avg_reproj_error_gt_model": round(
np.nanmean(report.reproj_error_gt_model[np.logical_not(report.v_corr_idxs_inlier_mask_gt)]),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right. I think it's cleaner to just compute it here when everything else is computed. I'll remove it here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Actually I'm going to open a different PR to cleaner up the TwoViewEstimationReport a little

rot3_angular_errors.append(report.R_error_deg)
trans_angular_errors.append(report.U_error_deg)
if report.R_error_deg is not None:
rot3_angular_errors.append(report.R_error_deg)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@travisdriver we shouldn't need this is not None check here, since we are just filling the array with None, and then casting it to float which converts them to Nan, and then they are ignored in the np.nanmean call.

Copy link
Collaborator

Choose a reason for hiding this comment

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

But if keeps the types simpler, maybe it's ok.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Was trying to fix some of the mypy errors

Copy link
Collaborator

Choose a reason for hiding this comment

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

Got it, sure.

gt_scene_mesh,
dist_threshold,
)
elif gt_wTi1 is not None and gt_wTi2 is not None:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator

@johnwlambert johnwlambert Nov 4, 2021

Choose a reason for hiding this comment

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

i think the double nesting here makes it harder to read. I prefer just checking to see if the mesh is there. or you can exit immediately with None,None.

We discuss this a but in Contributing.md, but returning early is always preferred to nesting.

# Compute ground truth metrics.
if v_corr_idxs_inlier_mask_gt is not None and reproj_error_gt_model is not None:
num_inliers_gt_model = np.count_nonzero(v_corr_idxs_inlier_mask_gt)
inlier_ratio_gt_model = (
Copy link
Collaborator

Choose a reason for hiding this comment

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

@travisdriver this is actually a different definition of the metric than the one we have been using. Before we were using the # of putatives as the denominator

https://github.com/borglab/gtsfm/pull/295/files#diff-1f5b63f35ed4d88709ce154ae9808f38a1a7178fb5f74211b1053ae5a8c37894L223

The way we had it before follows Heinly12eccv:
https://www.cs.unc.edu/~jheinly/publications/eccv2012-heinly.pdf

If we try to compute inlier_ratio_gt_model here, we don't have access to the # of putatives (corr_idxs instead of v_corr_idxs)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe that metric should be moved to the matcher, actually, since it's a matcher-based metric, and has nothing to do with the verifier.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like the fraction here is (#verified w.r.t. GT model) / (# verified w.r.t. estimated model), which I don't think is what we want for inlier ratio, since it could be greater than 1.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, it looks like we were computing it this way before, since we were passing in v_corr_idxs to compute_correspondence_metrics(), not corr_idxs.

Copy link
Collaborator

@johnwlambert johnwlambert Nov 5, 2021

Choose a reason for hiding this comment

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

So it looks like the inlier_ratio_gt_model here is actually

inlier_ratio_gt_model = (# verified correspondences that were right w.r.t. GT model) / # verrified correspondences

Copy link
Collaborator

Choose a reason for hiding this comment

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

@akshay-krishnan I think this explains the bug we saw in #306, actually

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use ground truth shape models and AstroNet data to evaluate frontend
4 participants