-
Notifications
You must be signed in to change notification settings - Fork 47
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
Adding expressive GTSFM data to allow non-consecutive cameras #113
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
49900ce
adding utils for graph processing
ayushbaid 5abe06b
Merge branch 'master' into feature/gtsam-data
ayushbaid 674d03e
adding gtsfm type for better camera persistance; fixing tests in DA
ayushbaid 15bbfa5
improving documentation
ayushbaid 6cb6cc1
Merge branch 'master' into feature/gtsam-data
ayushbaid 110c289
adding note about the requirement for a new class
ayushbaid 1af7ec3
fixing colmap IO
ayushbaid 7d5a33f
using folder path instead of file path
ayushbaid 63f9404
making dirs
ayushbaid f5db6b1
fixing file name in docstring
ayushbaid fa22221
fixing string formatting while dumping data
ayushbaid a6b5343
using save_dir keyword in creating dask nodes
ayushbaid 5cbf820
Merge branch 'master' into feature/gtsam-data
ayushbaid a292c9c
adding detailed comment about camera connectivity from tracks
ayushbaid 76b91df
using named arg for reproj error
ayushbaid eb7ea4a
formatting fixes
ayushbaid e015093
fixing variable names and formatting
ayushbaid 3b1d845
removing dummy DA
ayushbaid c0394c1
renaming flag to save gtsfm data
ayushbaid f563a25
using f-format while writing to disk
ayushbaid e8f137f
adding docstring for bal format
ayushbaid 5f478b4
ignoring results and metrics dir
ayushbaid eecdff3
improving readability
ayushbaid c7b0997
adding comment about mocking
ayushbaid 5df179e
switching argument order and using named args for readability
ayushbaid 06651a1
removing empty line
ayushbaid 1b4ce4c
correcting return type for getter for all camera poses
ayushbaid 7ebcb22
enhancing test for dropping disconnected components
ayushbaid 1ce6f96
reusing the graph utility function for connected component
ayushbaid cd72aa8
conerting the camera subset selection method to a class method
ayushbaid 3b0689f
renaming function
ayushbaid File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
"""Class to hold the tracks and cameras of a 3D scene. | ||
|
||
Authors: Ayush Baid | ||
""" | ||
import itertools | ||
from typing import Dict, List, Optional | ||
|
||
import numpy as np | ||
from gtsam import PinholeCameraCal3Bundler, SfmTrack | ||
|
||
import gtsfm.utils.graph as graph_utils | ||
|
||
EQUALITY_TOLERANCE = 1e-5 | ||
|
||
|
||
class GtsfmData: | ||
"""Class containing cameras and tracks, essentially describing the complete 3D scene. | ||
|
||
This class is needed over GTSAM's SfmData type because GTSAM's type does not allow for non-contiguous cameras. | ||
The situation of non-contiguous cameras can exists because of failures in front-end. | ||
""" | ||
|
||
def __init__(self, number_images: int) -> None: | ||
"""Initializes the class. | ||
|
||
Args: | ||
number_images: number of images/cameras in the scene. | ||
""" | ||
self._cameras: Dict[int, PinholeCameraCal3Bundler] = {} | ||
self._tracks: List[SfmTrack] = [] | ||
self._number_images = number_images | ||
|
||
def __eq__(self, other: object) -> bool: | ||
"""Checks equality with the other object.""" | ||
|
||
if not isinstance(other, GtsfmData): | ||
return False | ||
|
||
if self._number_images != other.number_images(): | ||
return False | ||
|
||
for i, cam in self._cameras.items(): | ||
other_cam = other.get_camera(i) | ||
if not cam.equals(other_cam, EQUALITY_TOLERANCE): | ||
return False | ||
|
||
for j in range(self.number_tracks()): | ||
track = self.get_track(j) | ||
other_track = other.get_track(j) | ||
|
||
if track.number_measurements() != other_track.number_measurements(): | ||
return False | ||
|
||
for k in range(track.number_measurements()): | ||
i, uv = track.measurement(k) | ||
other_i, other_uv = other_track.measurement(k) | ||
|
||
if i != other_i: | ||
return False | ||
if not np.allclose(uv, other_uv): | ||
return False | ||
|
||
return True | ||
|
||
def number_images(self) -> int: | ||
"""Getter for the number of images. | ||
|
||
Returns: | ||
Number of images. | ||
""" | ||
return self._number_images | ||
|
||
def number_tracks(self) -> int: | ||
"""Getter for the number of tracks. | ||
|
||
Returns: | ||
Number of tracks. | ||
""" | ||
return len(self._tracks) | ||
|
||
def get_valid_camera_indices(self) -> List[int]: | ||
"""Getter for image indices where there is a valid (not None) camera. | ||
|
||
Returns: | ||
List of indices with a valid camera. | ||
""" | ||
return list(self._cameras.keys()) | ||
|
||
def get_camera(self, index: int) -> Optional[PinholeCameraCal3Bundler]: | ||
"""Getter for camera. | ||
|
||
Args: | ||
index: the image index to fetch the camera for. | ||
|
||
Returns: | ||
The camera if it is a valid one, None otherwise. | ||
""" | ||
return self._cameras.get(index) | ||
|
||
def get_track(self, index: int) -> SfmTrack: | ||
"""Getter for the track. | ||
|
||
Args: | ||
index: track index to fetch. | ||
|
||
Returns: | ||
Requested track. | ||
""" | ||
return self._tracks[index] | ||
|
||
def add_track(self, track: SfmTrack) -> bool: | ||
"""Add a track, after checking if all the cameras in the track are already added. | ||
|
||
Args: | ||
track: track to add. | ||
|
||
Returns: | ||
Flag indicating the success of adding operation. | ||
""" | ||
# check if all cameras are already added | ||
for j in range(track.number_measurements()): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ayushbaid the changes looks great. i think this one is still outstanding, though |
||
i, _ = track.measurement(j) | ||
|
||
if i not in self._cameras: | ||
return False | ||
|
||
self._tracks.append(track) | ||
return True | ||
|
||
def add_camera(self, index: int, camera: PinholeCameraCal3Bundler,) -> None: | ||
"""Adds a camera. | ||
|
||
Args: | ||
index: the index associated with this camera. | ||
camera: camera object to it. | ||
|
||
Raises: | ||
ValueError: if the camera to be added is not a valid camera object. | ||
""" | ||
if camera is None: | ||
raise ValueError("Camera cannot be None, should be a valid camera") | ||
self._cameras[index] = camera | ||
|
||
def select_largest_connected_component(self) -> "GtsfmData": | ||
"""Selects the subset of data belonging to the largest connected component of the graph where the edges are | ||
between cameras which feature in the same track. | ||
|
||
Returns: | ||
New GtSfmData object with the subset of tracks and cameras. | ||
""" | ||
camera_edges = [] | ||
for sfm_track in self._tracks: | ||
cameras_in_use = [] | ||
for m_idx in range(sfm_track.number_measurements()): | ||
i, _ = sfm_track.measurement(m_idx) | ||
cameras_in_use.append(i) | ||
|
||
# Recreate track connectivity from track information | ||
# For example: a track has cameras [0, 2, 5]. In that case we will add pairs (0, 2), (0, 5), (2, 5) | ||
camera_edges += list(itertools.combinations(cameras_in_use, 2)) | ||
ayushbaid marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if len(camera_edges) == 0: | ||
return GtsfmData(self._number_images) | ||
|
||
cameras_in_largest_cc = graph_utils.get_nodes_in_largest_connected_component(camera_edges) | ||
return GtsfmData.from_selected_cameras(self, cameras_in_largest_cc) | ||
|
||
@classmethod | ||
def from_selected_cameras(cls, gtsfm_data: "GtsfmData", camera_indices: List[int]) -> "GtsfmData": | ||
"""Selects the cameras in the input list and the tracks associated with those cameras. | ||
|
||
Args: | ||
gtsfm_data: data to pick the cameras from. | ||
camera_indices: camera indices to select and keep in the new data. | ||
|
||
Returns: | ||
New object with the selected cameras and associated tracks. | ||
""" | ||
new_data = cls(gtsfm_data.number_images()) | ||
|
||
for i in gtsfm_data.get_valid_camera_indices(): | ||
if i in camera_indices: | ||
new_data.add_camera(i, gtsfm_data.get_camera(i)) | ||
|
||
new_camera_indices = new_data.get_valid_camera_indices() | ||
|
||
# add tracks which have all the camera present in new data | ||
for j in range(gtsfm_data.number_tracks()): | ||
track = gtsfm_data.get_track(j) | ||
is_valid = True | ||
for k in range(track.number_measurements()): | ||
i, _ = track.measurement(k) | ||
if i not in new_camera_indices: | ||
is_valid = False | ||
break | ||
if is_valid: | ||
new_data.add_track(track) | ||
|
||
return new_data |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 does this class need to know
number_images
?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 we can have 5 images, but just 3 cameras optimized by SfM
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.
right. is it required information inside the class though? i.e. could we do without it?