Skip to content

Commit

Permalink
fix merge conflict
Browse files Browse the repository at this point in the history
  • Loading branch information
senselessdev1 committed Mar 7, 2024
2 parents 5a385dd + 841bf4b commit 0d7f5cd
Show file tree
Hide file tree
Showing 25 changed files with 1,558 additions and 1,031 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/benchmark-self-hosted.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
steps:
- uses: actions/[email protected]
- name: Cache frontend
uses: actions/cache@v3
uses: actions/cache@v4
env:
# Increase this value to reset cache
CACHE_NUMBER_FRONTEND: 0
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
steps:
- uses: actions/[email protected]
- name: Cache frontend
uses: actions/cache@v3
uses: actions/cache@v4
env:
# Increase this value to reset cache
CACHE_NUMBER_FRONTEND: 0
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ output/

# Cache dir
cache/
*.mat

# Dev notebooks
notebooks/
2 changes: 1 addition & 1 deletion environment_linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dependencies:
- opencv-python>=4.5.4.58
- pydegensac
- colour
- pycolmap>=0.1.0
- pycolmap==0.4.0
- trimesh[easy]
- gtsam==4.2
- pydot
2 changes: 1 addition & 1 deletion environment_linux_cpuonly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dependencies:
- opencv-python>=4.5.4.58
- pydegensac
- colour
- pycolmap>=0.1.0
- pycolmap==0.4.0
- trimesh[easy]
- gtsam==4.2
- pydot
5 changes: 3 additions & 2 deletions gtsfm/averaging/rotation/rotation_averaging_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Authors: Jing Wu, Ayush Baid
"""

import abc
import time
from typing import Dict, List, Optional, Tuple
Expand All @@ -11,7 +12,7 @@
from dask.delayed import Delayed
from gtsam import Pose3, Rot3

import gtsfm.utils.geometry_comparisons as comp_utils
import gtsfm.utils.alignment as alignment_utils
import gtsfm.utils.metrics as metric_utils
from gtsfm.common.pose_prior import PosePrior
from gtsfm.evaluation.metrics import GtsfmMetric, GtsfmMetricsGroup
Expand Down Expand Up @@ -106,7 +107,7 @@ def evaluate(self, wRi_computed: List[Optional[Rot3]], wTi_gt: List[Optional[Pos
if len(wRi_computed) != len(wRi_gt):
raise ValueError("Lengths of wRi_list and gt_wRi_list should be the same.")

wRi_aligned = comp_utils.align_rotations(wRi_gt, wRi_computed)
wRi_aligned = alignment_utils.align_rotations(wRi_gt, wRi_computed)

metrics = []
metrics.append(GtsfmMetric(name="num_rotations_computed", data=len([x for x in wRi_computed if x is not None])))
Expand Down
5 changes: 3 additions & 2 deletions gtsfm/averaging/translation/averaging_1dsfm.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Authors: Jing Wu, Ayush Baid, Akshay Krishnan
"""

import time
import timeit
from collections import defaultdict
Expand All @@ -35,7 +36,7 @@
)

import gtsfm.common.types as gtsfm_types
import gtsfm.utils.geometry_comparisons as comp_utils
import gtsfm.utils.alignment as alignment_utils
import gtsfm.utils.logger as logger_utils
import gtsfm.utils.metrics as metrics_utils
import gtsfm.utils.sampling as sampling_utils
Expand Down Expand Up @@ -626,7 +627,7 @@ def compute_metrics(
wTi_list.append(None)
else:
wTi_list.append(Pose3(wRi, wti))
wTi_aligned_list, _ = comp_utils.align_poses_sim3_ignore_missing(gt_wTi_list, wTi_list)
wTi_aligned_list, _ = alignment_utils.align_poses_sim3_ignore_missing(gt_wTi_list, wTi_list)
wti_aligned_list = [wTi.translation() if wTi is not None else None for wTi in wTi_aligned_list]
gt_wti_list = [gt_wTi.translation() if gt_wTi is not None else None for gt_wTi in gt_wTi_list]

Expand Down
6 changes: 4 additions & 2 deletions gtsfm/bundle/bundle_adjustment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Authors: Xiaolong Wu, John Lambert, Ayush Baid
"""

import logging
import time
from collections import Counter
Expand All @@ -28,6 +29,7 @@
)

import gtsfm.common.types as gtsfm_types
import gtsfm.utils.alignment as alignment_utils
import gtsfm.utils.metrics as metrics_utils
import gtsfm.utils.tracks as track_utils
from gtsfm.common.gtsfm_data import GtsfmData
Expand Down Expand Up @@ -511,9 +513,9 @@ def evaluate(
return ba_metrics

# Align the sparse multi-view estimate after BA to the ground truth pose graph.
aligned_filtered_data = filtered_data.align_via_Sim3_to_poses(wTi_list_ref=poses_gt)
aligned_filtered_data = alignment_utils.align_gtsfm_data_via_Sim3_to_poses(filtered_data, wTi_list_ref=poses_gt)
ba_pose_error_metrics = metrics_utils.compute_ba_pose_metrics(
gt_wTi_list=poses_gt, ba_output=aligned_filtered_data, save_dir=save_dir
gt_wTi_list=poses_gt, computed_wTi_list=aligned_filtered_data.get_camera_poses(), save_dir=save_dir
)
ba_metrics.extend(metrics_group=ba_pose_error_metrics)

Expand Down
19 changes: 1 addition & 18 deletions gtsfm/common/gtsfm_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Authors: Ayush Baid, John Lambert, Xiaolong Wu
"""

import itertools
import logging
from typing import Any, Dict, List, Optional, Tuple
Expand All @@ -12,7 +13,6 @@
from gtsam import Pose3, SfmTrack, Similarity3

import gtsfm.common.types as gtsfm_types
import gtsfm.utils.geometry_comparisons as geometry_comparisons
import gtsfm.utils.graph as graph_utils
import gtsfm.utils.reprojection as reproj_utils

Expand Down Expand Up @@ -426,23 +426,6 @@ def filter_landmarks(self, reproj_err_thresh: float = 5) -> Tuple["GtsfmData", L

return filtered_data, valid_mask

def align_via_Sim3_to_poses(self, wTi_list_ref: List[Optional[Pose3]]) -> "GtsfmData":
"""Align estimated, sparse multiview result (self) to a set of reference poses.
Args:
wTi_list_ref: list of reference/target camera poses, ordered by camera index.
Returns:
aligned_data: sparse multiview result that is aligned to the poses above.
"""
# these are the estimated poses (source, to be aligned)
wTi_list = self.get_camera_poses()
# align the poses which are valid (i.e. are not None)
# some camera indices may have been lost after pruning to largest connected component, leading to None values
# rSe aligns the estimate `e` frame to the reference `r` frame
_, rSe = geometry_comparisons.align_poses_sim3_ignore_missing(wTi_list_ref, wTi_list)
return self.apply_Sim3(aSb=rSe)

def apply_Sim3(self, aSb: Similarity3) -> "GtsfmData":
"""Assume current tracks and cameras are in frame "b", then transport them to frame "a".
Expand Down
124 changes: 124 additions & 0 deletions gtsfm/evaluation/compare_colmap_outputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""Script to compare two reconstructions in Colmap's output format.
Authors: Ayush Baid
"""

import argparse
import os
from typing import Dict, List

import numpy as np
import pycolmap
from gtsam import Pose3, Similarity3
from scipy.spatial.transform import Rotation

import gtsfm.runner.gtsfm_runner_base as runner_base
import gtsfm.utils.alignment as alignment_utils
import gtsfm.utils.io as io_utils
import gtsfm.utils.logger as logger_utils
import gtsfm.utils.metrics as metric_utils
from gtsfm.evaluation.metrics import GtsfmMetricsGroup

logger = logger_utils.get_logger()


def load_poses(colmap_dirpath: str) -> Dict[str, Pose3]:
wTi_list, img_fnames, _, _, _, _ = io_utils.read_scene_data_from_colmap_format(colmap_dirpath)

return dict(zip(img_fnames, wTi_list))


def compare_poses(baseline_dirpath: str, eval_dirpath: str, output_dirpath: str) -> None:
"""Compare the pose metrics between two reconstructions (Colmap format).
Args:
baseline_dirpath: Directory with baseline reconstruction.
current_dirpath: Directory with reconstruction which needs evaluation.
output_dirpath: Directory to save the metrics.
"""
baseline_wTi_dict = load_poses(baseline_dirpath)
current_wTi_dict = load_poses(eval_dirpath)

common_fnames = baseline_wTi_dict.keys() & current_wTi_dict.keys()

if args.use_pycolmap_alignment:
current_reconstruction = pycolmap.Reconstruction(eval_dirpath)
baselineScurrent = Similarity3(
current_reconstruction.align_robust(
list(baseline_wTi_dict.keys()),
[wTi.translation() for wTi in baseline_wTi_dict.values()],
min_common_images=3,
).matrix
)
current_wTi_dict = {fname: baselineScurrent.transformFrom(wTi) for fname, wTi in current_wTi_dict.items()}

aRb = baselineScurrent.rotation().matrix()
atb = baselineScurrent.translation()
rz, ry, rx = Rotation.from_matrix(aRb).as_euler("zyx", degrees=True)
logger.info("Sim(3) Rotation `aRb`: rz=%.2f deg., ry=%.2f deg., rx=%.2f deg.", rz, ry, rx)
logger.info(f"Sim(3) Translation `atb`: [tx,ty,tz]={str(np.round(atb,2))}")
logger.info("Sim(3) Scale `asb`: %.2f", float(baselineScurrent.scale()))

logger.info(
"Baseline: %d, current: %d , common: %d poses",
len(baseline_wTi_dict),
len(current_wTi_dict),
len(common_fnames),
)

baseline_wTi_list: List[Pose3] = []
current_wTi_list: List[Pose3] = []
for fname, wTi in baseline_wTi_dict.items():
baseline_wTi_list.append(wTi)
current_wTi_list.append(current_wTi_dict.get(fname))

if not args.use_pycolmap_alignment:
current_wTi_list, _ = alignment_utils.align_poses_sim3_ignore_missing(baseline_wTi_list, current_wTi_list)

i2Ui1_dict_gt = metric_utils.get_twoview_translation_directions(baseline_wTi_list)

wRi_aligned_list, wti_aligned_list = metric_utils.get_rotations_translations_from_poses(current_wTi_list)
baseline_wRi_list, baseline_wti_list = metric_utils.get_rotations_translations_from_poses(baseline_wTi_list)

metrics = []
metrics.append(metric_utils.compute_rotation_angle_metric(wRi_aligned_list, baseline_wRi_list))
metrics.append(metric_utils.compute_translation_distance_metric(wti_aligned_list, baseline_wti_list))
metrics.append(metric_utils.compute_relative_translation_angle_metric(i2Ui1_dict_gt, current_wTi_list))
metrics.append(metric_utils.compute_translation_angle_metric(baseline_wTi_list, current_wTi_list))

rotation_angular_errors = metrics[0]._data
translation_angular_errors = metrics[3]._data
metrics.extend(
metric_utils.compute_pose_auc_metric(
rotation_angular_errors, translation_angular_errors, save_dir=output_dirpath
)
)

ba_pose_metrics = GtsfmMetricsGroup(name="ba_pose_error_metrics", metrics=metrics)

runner_base.save_metrics_reports([ba_pose_metrics], metrics_path=output_dirpath)


if __name__ == "__main__":
# Compare two reconstructions (in Colmap's output format). Right now, we just compare the poses.

parser = argparse.ArgumentParser()
parser.add_argument(
"--baseline",
required=True,
help="Path to directory containing benchmark artifacts for the baseline",
)
parser.add_argument(
"--current",
required=True,
help="Path to directory containing benchmark artifacts for the current",
)
parser.add_argument("--output", required=True, help="Output for the json file for pose metrics")
parser.add_argument(
"--use_pycolmap_alignment", action="store_true", help="Use Pycolmap to align cameras between two reconstruction"
)
args = parser.parse_args()

os.makedirs(args.output, exist_ok=True)

compare_poses(args.baseline, args.current, args.output)
4 changes: 3 additions & 1 deletion gtsfm/multi_view_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Authors: Ayush Baid, John Lambert
"""

from typing import Dict, List, Optional, Tuple

import dask
Expand All @@ -13,6 +14,7 @@

import gtsfm.common.types as gtsfm_types
import gtsfm.utils.graph as graph_utils
import gtsfm.utils.alignment as alignment_utils
from gtsfm.averaging.rotation.rotation_averaging_base import RotationAveragingBase
from gtsfm.averaging.translation.translation_averaging_base import TranslationAveragingBase
from gtsfm.bundle.global_ba import GlobalBundleAdjustment
Expand Down Expand Up @@ -165,7 +167,7 @@ def create_computation_graph(
]

# Align the sparse multi-view estimate before BA to the ground truth pose graph.
ba_input_graph = dask.delayed(ba_input_graph.align_via_Sim3_to_poses)(gt_wTi_list)
ba_input_graph = dask.delayed(alignment_utils.align_gtsfm_data_via_Sim3_to_poses)(ba_input_graph, gt_wTi_list)

return ba_input_graph, ba_result_graph, viewgraph_two_view_reports_graph, multiview_optimizer_metrics_graph

Expand Down
8 changes: 3 additions & 5 deletions gtsfm/runner/gtsfm_runner_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def run(self) -> GtsfmData:
local_cluster_kwargs = {
"n_workers": self.parsed_args.num_workers,
"threads_per_worker": self.parsed_args.threads_per_worker,
"dashboard_address": self.parsed_args.dashboard_port
"dashboard_address": self.parsed_args.dashboard_port,
}
if self.parsed_args.worker_memory_limit is not None:
local_cluster_kwargs["memory_limit"] = self.parsed_args.worker_memory_limit
Expand Down Expand Up @@ -414,9 +414,7 @@ def run(self) -> GtsfmData:
return sfm_result


def unzip_two_view_results(
two_view_results: Dict[Tuple[int, int], TWO_VIEW_OUTPUT]
) -> Tuple[
def unzip_two_view_results(two_view_results: Dict[Tuple[int, int], TWO_VIEW_OUTPUT]) -> Tuple[
Dict[Tuple[int, int], Rot3],
Dict[Tuple[int, int], Unit3],
Dict[Tuple[int, int], np.ndarray],
Expand All @@ -436,7 +434,7 @@ def unzip_two_view_results(
i2Ri1 = two_view_output[0]
i2Ui1 = two_view_output[1]
if i2Ri1 is None or i2Ui1 is None:
print(f"Skip {i1},{i2} since None")
logger.debug("Skip %d, %d since None", i1, i2)
continue

i2Ri1_dict[(i1, i2)] = i2Ri1
Expand Down
10 changes: 6 additions & 4 deletions gtsfm/scene_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Authors: Ayush Baid, John Lambert
"""

import logging
import os
import shutil
Expand All @@ -14,23 +15,24 @@
import matplotlib.pyplot as plt
import numpy as np
from dask.delayed import Delayed
from gtsam import Pose3, Similarity3, Rot3, Unit3
from gtsam import Pose3, Rot3, Similarity3, Unit3
from trimesh import Trimesh

import gtsfm.common.types as gtsfm_types
import gtsfm.two_view_estimator as two_view_estimator
import gtsfm.utils.alignment as alignment_utils
import gtsfm.utils.ellipsoid as ellipsoid_utils
import gtsfm.utils.io as io_utils
import gtsfm.utils.logger as logger_utils
import gtsfm.utils.viz as viz_utils
from gtsfm.common.gtsfm_data import GtsfmData
from gtsfm.common.image import Image
from gtsfm.retriever.image_pairs_generator import ImagePairsGenerator
from gtsfm.common.keypoints import Keypoints
from gtsfm.common.pose_prior import PosePrior
from gtsfm.densify.mvs_base import MVSBase
from gtsfm.frontend.correspondence_generator.correspondence_generator_base import CorrespondenceGeneratorBase
from gtsfm.multi_view_optimizer import MultiViewOptimizer
from gtsfm.retriever.image_pairs_generator import ImagePairsGenerator
from gtsfm.retriever.retriever_base import ImageMatchingRegime
from gtsfm.two_view_estimator import (
POST_ISP_REPORT_TAG,
Expand Down Expand Up @@ -297,8 +299,8 @@ def align_estimated_gtsfm_data(
Updated ba_output GtsfmData object aligned to axes.
Updated gt_pose_graph with GT poses aligned to axes.
"""
ba_input = ba_input.align_via_Sim3_to_poses(gt_wTi_list)
ba_output = ba_output.align_via_Sim3_to_poses(gt_wTi_list)
ba_input = alignment_utils.align_gtsfm_data_via_Sim3_to_poses(ba_input, gt_wTi_list)
ba_output = alignment_utils.align_gtsfm_data_via_Sim3_to_poses(ba_output, gt_wTi_list)

walignedTw = ellipsoid_utils.get_ortho_axis_alignment_transform(ba_output)
walignedSw = Similarity3(R=walignedTw.rotation(), t=walignedTw.translation(), s=1.0)
Expand Down
2 changes: 2 additions & 0 deletions gtsfm/two_view_estimator_cacher.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def __generate_cache_key(
self, keypoints_i1: Keypoints, keypoints_i2: Keypoints, putative_corr_idxs: np.ndarray
) -> str:
"""Generates a cache key according to keypoint coordinates and putative correspondence indices."""
if putative_corr_idxs.size == 0: # catch no correspondences
return cache_utils.generate_hash_for_numpy_array(np.array([]))

# Subsample correspondence indices.
sampled_idxs = putative_corr_idxs[:NUM_CORRESPONDENCES_TO_SAMPLE_FOR_HASH]
Expand Down
Loading

0 comments on commit 0d7f5cd

Please sign in to comment.