-
Notifications
You must be signed in to change notification settings - Fork 52
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
Expanding gtsfm data #376
base: master
Are you sure you want to change the base?
Expanding gtsfm data #376
Conversation
Have not looked at code, but just from your description, the ground truth scene and the computed scene are actually not the same reconstruction, so would it be better if we used a different GtsfmData instance for ground truth? Gtsfm is currently in development and GT and metrics are everywhere. But once we get to a stable state, GtsfmData is our result which I think should be free from any GT members. |
We could maybe put all GT members in a different GtsfmData or maybe have another container GTSfMGTData, curious what @ayushbaid thinks |
Agree with Akshay. It doesn't make sense to have additional information on this datastructure. We can have two if needed. The API design and the dataclasses have to be clean, and just have the arguments/attributes which are obvious. GtsfmData is our output, which describes a 3d scene completely. It should not have attributes which are not needed to visualize a 3d scene. |
For any evaluation we need for the astronet dataset or any other dataset, we can load the gtsfmdata from disk, construct the loader, and compare them. |
I like the idea of having 1 class that holds a complete result.
If MVS produces a mesh, that I think we could safely assume the mesh could also be an attribute of a "complete result". |
Thank you guys for the feedback. @akshay-krishnan Sorry, I think my description was a little misleading. The main change I made was having the loader create an instance of The only thing I added to the |
Sounds good. Does @codyly also plan to use the same trimesh format for dense reconstruction? |
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.
Some comments, please let us know when the PR is ready to review/submit
gtsfm/loader/astronet_loader.py
Outdated
@@ -78,7 +79,7 @@ def __init__( | |||
if not Path(data_dir).exists(): | |||
raise FileNotFoundError("No data found at %s." % data_dir) | |||
cameras, images, points3d = colmap_io.read_model(path=data_dir, ext=".bin") | |||
self._calibrations, self._wTi_list, img_fnames, self._sfmtracks = self.colmap2gtsfm( | |||
self._calibrations, self._wTi_list, img_fnames, self._sfmtracks, cameras_gtsfm = self.colmap2gtsfm( |
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.
seeing wTi and cameras together is always a red flag ..
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 elaborate?
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.
cameras contain poses, camera.pose(), so wTi is not necessary
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 could be said for the calibrations.
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.
true, so why are we returning everything here?
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, I forgot to push a commit that removed cameras_gtsfm
; will do that.
What I meant was that even in the SceneOptimizer we are passing in calibrations and cameras. However, this is hard to avoid since it is not possible to set either the Pose or Calibration to None in PinholeCameraCal3Bundler
.
So, the two options are (1) pass around Calibration(s) and Pose(s) separately and construct cameras as needed, or (2) pass around Calibration(s) and Camera(s) which have redundant information.
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.
removed cameras_gtsfm
@property | ||
def gt_gtsfm_data(self): | ||
return self._gt_gtsfm_data | ||
|
||
@staticmethod | ||
def colmap2gtsfm( |
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 this a static method in this class? like what does it have to do with astronet specifically?
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.
AstroNet data is in COLMAP format. I could move this to the base class since it would also be useful for the Colmap data?
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 dont feel like it is related to the loader, maybe it can be moved to a util file. Why would a user of a loader want to convert colmap data to gtsfm format? I would assume it just provides the gtsfm format, using whatever underneath.
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.
Because the AstroNet data is in Colmap format and it needs to be converted to GTSfM format before it can be used by the pipeline.
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 mean the loader is supposed to abstract that away. I think this PR #346 is moving it to a util? Which is better I think
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.
The loader is responsible for reading in the specific data and converting it to a format usable by GTSfM.
I don't think it makes sense to have to add a util in some other file for every dataset used, as this will just congest the other modules. However, I think it's fine to move this specific function to a util, as we work with the Colmap format a lot.
When other users want to apply GTSfM to their specific data, all they should need to do is fill in a new loader (and maybe a runner) without touching any other code. It's infeasible to expect our main code base to handle converting every single dataset to the expected GTSfM format.
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.
However, I think it's fine to move this specific function to a util, as we work with the Colmap format a lot.
yes, that is my reason as well.
I don't see why anyone would use the Astronet loader to convert colmap data to gtsfm. It already provides data in gtsfm format. And even if we did want to convert colmap to gtsfm format for some other reason, it does not need a AstroNet loader.
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.
#346 is trying to move it to io_utils.py, which I think is better than a static method in AstroNet loader (my point that its being used in a non-loader specific context).
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.
Maybe I should just leave it as is here to avoid conflicts with #346?
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.
SInce youre changing the API and Jon isn't I think we should merge this is first.
@womackj1 Can #346 wait until this is merged? I don't see any updates on it recently.
# Build cameras graph from GT GtsfmData. | ||
# TODO (): remove later; this is just to conform to the required input of other functions. | ||
gt_cameras_graph = ( | ||
[dask.delayed(gt_gtsfm_data.get_camera)(i) for i in gt_gtsfm_data._cameras.keys()] |
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.
Maybe I should ask @ayushbaid , but what is our "standard representation" for cameras? Is it dicts or lists? why are converting one to another here?
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.
GtsfmData uses dicts to account for noncontiguous cameras ids, as some frames may be rejected during the verification process.
I convert the to lists here because the rest of the code expects it as a list, but I do agree that it's dangerous to have the same data as different data types.
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.
Yeah, so maybe we should update the rest of the 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.
I spent a while trying to update the code so that we do not rely on converting the dictionary of cameras in GtsfmData
to a List. However, almost all of the metric computation functions rely on Lists of Pose derived from the camera dictionary of GtsfmData.
I'm assuming this is why the _number_images
attribute of the GtsfmData
class was added in the first place: so that the cameras data could be converted to a list such that its the same length as the original ground truth data. I think this is should be saved for another PR.
I do think that using Dicts instead of Lists like Colmap is a lot better and should be implemented in the near future.
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.
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.
Similarly, I think _tracks
should be a dictionary as well.
gtsfm/loader/astronet_loader.py
Outdated
@@ -78,7 +79,7 @@ def __init__( | |||
if not Path(data_dir).exists(): | |||
raise FileNotFoundError("No data found at %s." % data_dir) | |||
cameras, images, points3d = colmap_io.read_model(path=data_dir, ext=".bin") | |||
self._calibrations, self._wTi_list, img_fnames, self._sfmtracks = self.colmap2gtsfm( | |||
self._calibrations, self._wTi_list, img_fnames, self._sfmtracks, cameras_gtsfm = self.colmap2gtsfm( |
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.
true, so why are we returning everything here?
@property | ||
def gt_gtsfm_data(self): | ||
return self._gt_gtsfm_data | ||
|
||
@staticmethod | ||
def colmap2gtsfm( |
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.
However, I think it's fine to move this specific function to a util, as we work with the Colmap format a lot.
yes, that is my reason as well.
I don't see why anyone would use the Astronet loader to convert colmap data to gtsfm. It already provides data in gtsfm format. And even if we did want to convert colmap to gtsfm format for some other reason, it does not need a AstroNet loader.
# Build cameras graph from GT GtsfmData. | ||
# TODO (): remove later; this is just to conform to the required input of other functions. | ||
gt_cameras_graph = ( | ||
[dask.delayed(gt_gtsfm_data.get_camera)(i) for i in gt_gtsfm_data._cameras.keys()] |
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.
@property | ||
def gt_gtsfm_data(self): | ||
return self._gt_gtsfm_data | ||
|
||
@staticmethod | ||
def colmap2gtsfm( |
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.
#346 is trying to move it to io_utils.py, which I think is better than a static method in AstroNet loader (my point that its being used in a non-loader specific context).
- removed `cameras_gtsfm` from `AstronetLoader` - removed commented code from `AstronetRunner` and added scattering of `scene_mesh` to `LoaderBase`
@property | ||
def gt_gtsfm_data(self): | ||
return self._gt_gtsfm_data | ||
|
||
@staticmethod | ||
def colmap2gtsfm( |
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.
SInce youre changing the API and Jon isn't I think we should merge this is first.
@womackj1 Can #346 wait until this is merged? I don't see any updates on it recently.
@@ -94,6 +92,13 @@ def create_computation_graph( | |||
Two view report w/ verifier metrics wrapped as Delayed. | |||
Two view report w/ post-processor metrics wrapped as Delayed. | |||
""" | |||
# Unpack GT data. | |||
if gt_gtsfm_data is not None: | |||
gt_wTi1_graph = gt_gtsfm_data.get_camera(0).pose() |
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.
instead of hard-coding camera 0 and 1, we should pull out i1
and i2
here
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.
If so, I would have to pass in i1
and i2
.
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 good with passing in i1 and i2. I think whenever we use a camera index in GTSFM, we need to be able to assume it really means the index we expect
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 don't see the utility in passing in two indices for a single object as opposed to just using a known mapping (i.e., i1 -> 0 and i2 -> 1), especially since none of the other objects use the indices.
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 partially agree with John. I think it might be best to accept gt_i1Ti2 and the gt_mesh as two arguments.
Passing i1
and i2
I think makes the API too messy. But yes, as you said (also in another comment), assuming the input has only 2 cameras at 0 and 1 could lead to bugs.
|
||
Ref: http://distributed.dask.org/en/stable/api.html#distributed.Client.scatter | ||
""" | ||
# TODO(travisdriver): find a way to scatter mesh that doesn't require copying the GtsfmData object. |
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.
where do we do the copy?
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.
Lines 99-103
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.
Any reason we can’t use copy.deepcopy?
def get_two_view_data(self, i1: int, i2: int) -> "GtsfmData": | ||
"""Collects GtsfmData for a single image pair.""" | ||
# TODO (travisdriver): also collect tracks if available. | ||
cameras_pair = {0: self.get_camera(i1), 1: self.get_camera(i2)} |
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 think we should always use i1
and i2
directly, instead of remapping to 0 and 1, since this can create some confusion/bugs downstream for the user.
@ayushbaid @akshay-krishnan what are your thoughts?
Thanks for the PR, Travis. Agreed that this needed some consolidation/ clean up. |
I'm seeing a few flake8 failures:
|
Disclaimer: currently only working on AstroNet data
The docstring for the
GtsfmData
class states that its purpose is "describing the complete 3D scene." Therefore, I thought it made sense to extend the class' functionality slightly to support describing the ground truth scene as well. That way we can just track a singleGtsfmData
object as opposed to tracking the various components of the ground truth scene:gt_cameras_graph
,gt_poses_graph
,gt_scene_mesh_graph
, etc.This is definitely not in its final form, but I wanted to get some feedback before I sunk too much time into refactoring things.