From abb923eed6bf16df51625d7de7d53f635e56508f Mon Sep 17 00:00:00 2001 From: Shaohui Liu Date: Thu, 9 Jan 2025 08:55:39 +0100 Subject: [PATCH] Switch to pycolmap.Reconstruction for model converter. Add colmap visualizer with the same backend (Open3D) (#121) --- .github/workflows/build.yml | 10 ++- limap/base/bindings.cc | 1 - limap/pointsfm/__init__.py | 6 +- limap/pointsfm/colmap_reader.py | 106 ++++++++----------------- limap/pointsfm/colmap_sfm.py | 5 +- limap/pointsfm/model_converter.py | 48 +++++------ limap/runners/functions_structures.py | 16 ++-- limap/runners/line_triangulation.py | 3 +- limap/visualize/__init__.py | 2 + limap/visualize/vis_utils.py | 21 +++++ pytest.ini | 4 + requirements.txt | 1 + tests/base/test_linebase.py | 17 ++++ tests/pointsfm/test_colmap_reader.py | 29 +++++++ tests/pointsfm/test_model_converter.py | 13 +++ tests/third-party/test_open3d.py | 14 ++++ tests/third-party/test_pycolmap.py | 13 +++ visualize_colmap_model.py | 68 ++++++++++++++++ 18 files changed, 258 insertions(+), 119 deletions(-) create mode 100644 pytest.ini create mode 100644 tests/base/test_linebase.py create mode 100644 tests/pointsfm/test_colmap_reader.py create mode 100644 tests/pointsfm/test_model_converter.py create mode 100644 tests/third-party/test_open3d.py create mode 100644 tests/third-party/test_pycolmap.py create mode 100644 visualize_colmap_model.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28f5c5c0..0ffbdf8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,6 +83,15 @@ jobs: - name: Run Python tests run: python -c "import limap; print(limap.__version__)" + - name: Run tests + run: | + pytest -m ci_workflow tests + + + - name: Run localization test + run: | + python runners/tests/localization.py + - name: Run E2E tests run: | bash scripts/quickstart.sh @@ -90,5 +99,4 @@ jobs: --output_dir outputs/quickstart_test --visualize 0 python runners/hypersim/triangulation.py --default_config_file cfgs/triangulation/default_fast.yaml \ --output_dir outputs/quickstart_test --triangulation.use_exhaustive_matcher --skip_exists --visualize 0 - python runners/tests/localization.py diff --git a/limap/base/bindings.cc b/limap/base/bindings.cc index 4d40068d..102d6a8f 100644 --- a/limap/base/bindings.cc +++ b/limap/base/bindings.cc @@ -678,7 +678,6 @@ void bind_line_linker(py::module &m) { } void bind_camera(py::module &m) { - // TODO: use pycolmap py::enum_ PyCameraModelId(m, "CameraModelId", py::module_local()); PyCameraModelId.value("INVALID", colmap::CameraModelId::kInvalid); diff --git a/limap/pointsfm/__init__.py b/limap/pointsfm/__init__.py index 82d55b7d..d75376b2 100644 --- a/limap/pointsfm/__init__.py +++ b/limap/pointsfm/__init__.py @@ -2,9 +2,9 @@ from _limap._pointsfm import * # noqa: F403 from .colmap_reader import ( - PyReadCOLMAP, ReadPointTracks, check_exists_colmap_model, + convert_colmap_to_imagecols, ) from .colmap_sfm import ( run_colmap_sfm, @@ -20,9 +20,9 @@ ) __all__ = [n for n in _pointsfm.__dict__ if not n.startswith("_")] + [ - "PyReadCOLMAP", - "ReadPointTracks", "check_exists_colmap_model", + "convert_colmap_to_imagecols", + "ReadPointTracks", "run_colmap_sfm", "run_colmap_sfm_with_known_poses", "run_hloc_matches", diff --git a/limap/pointsfm/colmap_reader.py b/limap/pointsfm/colmap_reader.py index 9f896c4d..b02d5535 100644 --- a/limap/pointsfm/colmap_reader.py +++ b/limap/pointsfm/colmap_reader.py @@ -1,19 +1,9 @@ import os -import sys +import pycolmap from _limap import _base from pycolmap import logging -sys.path.append(os.path.dirname(os.path.abspath(__file__))) -from hloc.utils.read_write_model import ( - read_cameras_binary, - read_cameras_text, - read_images_binary, - read_images_text, - read_points3D_binary, - read_points3D_text, -) - def check_exists_colmap_model(model_path): if ( @@ -29,29 +19,12 @@ def check_exists_colmap_model(model_path): ) -def ReadInfos(colmap_path, model_path="sparse", image_path="images"): - logging.info("Start loading COLMAP sparse reconstruction.") - model_path = os.path.join(colmap_path, model_path) - image_path = os.path.join(colmap_path, image_path) - if os.path.exists(os.path.join(model_path, "cameras.bin")): - fname_cameras = os.path.join(model_path, "cameras.bin") - fname_images = os.path.join(model_path, "images.bin") - colmap_cameras = read_cameras_binary(fname_cameras) - colmap_images = read_images_binary(fname_images) - elif os.path.exists(os.path.join(model_path, "cameras.txt")): - fname_cameras = os.path.join(model_path, "cameras.txt") - fname_images = os.path.join(model_path, "images.txt") - colmap_cameras = read_cameras_text(fname_cameras) - colmap_images = read_images_text(fname_images) - else: - raise ValueError( - f"Error! The model file does not exist at {model_path}" - ) - logging.info(f"Reconstruction loaded. (n_images = {len(colmap_images)})") - +def convert_colmap_to_imagecols( + reconstruction: pycolmap.Reconstruction, image_path=None +): # read cameras cameras = {} - for cam_id, colmap_cam in colmap_cameras.items(): + for cam_id, colmap_cam in reconstruction.cameras.items(): cameras[cam_id] = _base.Camera( colmap_cam.model, colmap_cam.params, @@ -61,13 +34,17 @@ def ReadInfos(colmap_path, model_path="sparse", image_path="images"): # read images camimages = {} - for img_id, colmap_image in colmap_images.items(): + for img_id, colmap_image in reconstruction.images.items(): imname = colmap_image.name cam_id = colmap_image.camera_id - pose = _base.CameraPose(colmap_image.qvec, colmap_image.tvec) - camimage = _base.CameraImage( - cam_id, pose, image_name=os.path.join(image_path, imname) + qvec_xyzw = colmap_image.cam_from_world.rotation.quat + qvec = [qvec_xyzw[3], qvec_xyzw[0], qvec_xyzw[1], qvec_xyzw[2]] + tvec = colmap_image.cam_from_world.translation + pose = _base.CameraPose(qvec, tvec) + image_name = ( + imname if image_path is None else os.path.join(image_path, imname) ) + camimage = _base.CameraImage(cam_id, pose, image_name=image_name) camimages[img_id] = camimage # get image collection @@ -75,46 +52,33 @@ def ReadInfos(colmap_path, model_path="sparse", image_path="images"): return imagecols -def PyReadCOLMAP(colmap_path, model_path=None): - if model_path is not None: - model_path = os.path.join(colmap_path, model_path) - else: - model_path = colmap_path - if os.path.exists(os.path.join(model_path, "points3D.bin")): - fname_cameras = os.path.join(model_path, "cameras.bin") - fname_points = os.path.join(model_path, "points3D.bin") - fname_images = os.path.join(model_path, "images.bin") - colmap_cameras = read_cameras_binary(fname_cameras) - colmap_images = read_images_binary(fname_images) - colmap_points = read_points3D_binary(fname_points) - elif os.path.exists(os.path.join(model_path, "points3D.txt")): - fname_cameras = os.path.join(model_path, "cameras.txt") - fname_points = os.path.join(model_path, "points3D.txt") - fname_images = os.path.join(model_path, "images.txt") - colmap_cameras = read_cameras_text(fname_cameras) - colmap_images = read_images_text(fname_images) - colmap_points = read_points3D_text(fname_points) - else: - raise ValueError( - f"Error! The model file does not exist at {model_path}" - ) - reconstruction = {} - reconstruction["cameras"] = colmap_cameras - reconstruction["images"] = colmap_images - reconstruction["points"] = colmap_points - return reconstruction +def ReadInfos(colmap_path, model_path="sparse", image_path="images"): + logging.info("Start loading COLMAP sparse reconstruction.") + model_path = os.path.join(colmap_path, model_path) + image_path = os.path.join(colmap_path, image_path) + reconstruction = pycolmap.Reconstruction(model_path) + logging.info( + f"Reconstruction loaded. (n_images = {reconstruction.num_images()}" + ) + imagecols = convert_colmap_to_imagecols( + reconstruction, image_path=image_path + ) + return imagecols -def ReadPointTracks(colmap_reconstruction): +def ReadPointTracks(reconstruction: pycolmap.Reconstruction): pointtracks = {} - for point3d_id, p in colmap_reconstruction["points"].items(): - p_image_ids, point2d_ids = p.image_ids, p.point2D_idxs + for point3d_id, p in reconstruction.points3D.items(): + p_image_ids = [] + point2d_ids = [] p2d_list = [] - for p_img_id, point2d_id in zip( - p_image_ids.tolist(), point2d_ids.tolist() - ): + for elem in p.track.elements: + p_image_ids.append(elem.image_id) + point2d_ids.append(elem.point2D_idx) p2d_list.append( - colmap_reconstruction["images"][p_img_id].xys[point2d_id] + reconstruction.images[elem.image_id] + .points2D[elem.point2D_idx] + .xy ) ptrack = _base.PointTrack(p.xyz, p_image_ids, point2d_ids, p2d_list) pointtracks[point3d_id] = ptrack diff --git a/limap/pointsfm/colmap_sfm.py b/limap/pointsfm/colmap_sfm.py index 4da3a425..3ddca072 100644 --- a/limap/pointsfm/colmap_sfm.py +++ b/limap/pointsfm/colmap_sfm.py @@ -1,18 +1,15 @@ import copy import os import shutil -import sys from pathlib import Path import cv2 import pycolmap from pycolmap import logging -sys.path.append(os.path.dirname(os.path.abspath(__file__))) -from model_converter import convert_imagecols_to_colmap - import hloc.utils.database as database import hloc.utils.read_write_model as colmap_utils +from limap.pointsfm.model_converter import convert_imagecols_to_colmap def import_images_with_known_cameras(image_dir, database_path, imagecols): diff --git a/limap/pointsfm/model_converter.py b/limap/pointsfm/model_converter.py index 3c6abff0..3090c2c3 100644 --- a/limap/pointsfm/model_converter.py +++ b/limap/pointsfm/model_converter.py @@ -1,45 +1,36 @@ import os -import sys -from limap.util.geometry import rotation_from_quaternion - -sys.path.append(os.path.dirname(os.path.abspath(__file__))) -from colmap_reader import PyReadCOLMAP +import pycolmap import hloc.utils.read_write_model as colmap_utils def convert_colmap_to_visualsfm(colmap_model_path, output_nvm_file): - reconstruction = PyReadCOLMAP(colmap_model_path) - colmap_cameras, colmap_images, colmap_points = ( - reconstruction["cameras"], - reconstruction["images"], - reconstruction["points"], - ) + reconstruction = pycolmap.Reconstruction(colmap_model_path) with open(output_nvm_file, "w") as f: f.write("NVM_V3\n\n") # write images - f.write(f"{len(colmap_images)}\n") + f.write(f"{reconstruction.num_images()}\n") map_image_id = dict() - for cnt, item in enumerate(colmap_images.items()): + for cnt, item in enumerate(reconstruction.images.items()): img_id, colmap_image = item map_image_id[img_id] = cnt img_name = colmap_image.name cam_id = colmap_image.camera_id - cam = colmap_cameras[cam_id] - if cam.model == "SIMPLE_PINHOLE": + cam = reconstruction.cameras[cam_id] + if cam.model == pycolmap.CameraModelId.SIMPLE_PINHOLE: assert cam.params[1] == 0.5 * cam.width assert cam.params[2] == 0.5 * cam.height focal = cam.params[0] k1 = 0.0 - elif cam.model == "PINHOLE": + elif cam.model == pycolmap.CameraModelId.PINHOLE: assert cam.params[0] == cam.params[1] assert cam.params[2] == 0.5 * cam.width assert cam.params[3] == 0.5 * cam.height focal = cam.params[0] k1 = 0.0 - elif cam.model == "SIMPLE_RADIAL": + elif cam.model == pycolmap.CameraModelId.SIMPLE_RADIAL: assert cam.params[1] == 0.5 * cam.width assert cam.params[2] == 0.5 * cam.height focal = cam.params[0] @@ -48,33 +39,34 @@ def convert_colmap_to_visualsfm(colmap_model_path, output_nvm_file): raise ValueError("Camera model not supported in VisualSfM.") f.write(f"{img_name}\t") f.write(f" {focal}") - qvec, tvec = colmap_image.qvec, colmap_image.tvec - R = rotation_from_quaternion(qvec) - center = -R.transpose() @ tvec + qvec_xyzw = colmap_image.cam_from_world.rotation.quat + qvec = [qvec_xyzw[3], qvec_xyzw[0], qvec_xyzw[1], qvec_xyzw[2]] + center = colmap_image.cam_from_world.inverse().translation f.write(f" {qvec[0]} {qvec[1]} {qvec[2]} {qvec[3]}") f.write(f" {center[0]} {center[1]} {center[2]}") f.write(f" {k1} 0\n") f.write("\n") # write points - f.write(f"{len(colmap_points)}\n") - for _, point in colmap_points.items(): + f.write(f"{reconstruction.num_points3D()}\n") + for _, point in reconstruction.points3D.items(): xyz = point.xyz + track = point.track f.write(f"{xyz[0]} {xyz[1]} {xyz[2]}") f.write(" 128 128 128") # dummy color - n_supports = len(point.image_ids) - f.write(f" {n_supports}") - for idx in range(n_supports): - img_id = point.image_ids[idx] - xy_id = point.point2D_idxs[idx] + f.write(f" {len(track.elements)}") + for _, elem in enumerate(track.elements): + img_id = elem.image_id + xy_id = elem.point2D_idx img_index = map_image_id[img_id] f.write(f" {img_index} {xy_id}") - xy = colmap_images[img_id].xys[xy_id] + xy = reconstruction.images[img_id].points2D[xy_id].xy f.write(f" {xy[0]} {xy[1]}") f.write("\n") def convert_imagecols_to_colmap(imagecols, colmap_output_path): + # TODO: change to pycolmap.Reconstruction ### make folders if not os.path.exists(colmap_output_path): os.makedirs(colmap_output_path) diff --git a/limap/runners/functions_structures.py b/limap/runners/functions_structures.py index 669dbeb8..96e8c506 100644 --- a/limap/runners/functions_structures.py +++ b/limap/runners/functions_structures.py @@ -1,6 +1,7 @@ import os import numpy as np +import pycolmap from pycolmap import logging from tqdm import tqdm @@ -78,19 +79,14 @@ def compute_colmap_model_with_junctions( def compute_2d_bipartites_from_colmap( - reconstruction, imagecols, all_2d_lines, cfg=None + reconstruction: pycolmap.Reconstruction, imagecols, all_2d_lines, cfg=None ): if cfg is None: cfg = dict() all_bpt2ds = {} cfg_bpt2d = structures.PL_Bipartite2dConfig(cfg) - colmap_cameras, colmap_images, colmap_points = ( - reconstruction["cameras"], - reconstruction["images"], - reconstruction["points"], - ) logging.info("Start computing 2D bipartites...") - for img_id, colmap_image in tqdm(colmap_images.items()): + for img_id, colmap_image in tqdm(reconstruction.images.items()): n_points = colmap_image.xys.shape[0] indexes = np.arange(0, n_points) xys = colmap_image.xys @@ -100,8 +96,8 @@ def compute_2d_bipartites_from_colmap( # resize xys if needed cam_id = imagecols.camimage(img_id).cam_id orig_size = ( - colmap_cameras[cam_id].width, - colmap_cameras[cam_id].height, + reconstruction.cameras[cam_id].width, + reconstruction.cameras[cam_id].height, ) cam = imagecols.cam(cam_id) new_size = (cam.w(), cam.h()) @@ -117,6 +113,6 @@ def compute_2d_bipartites_from_colmap( ) all_bpt2ds[img_id] = bpt2d points = {} - for point3d_id, p in tqdm(colmap_points.items()): + for point3d_id, p in tqdm(reconstruction.points3D.items()): points[point3d_id] = p.xyz return all_bpt2ds, points diff --git a/limap/runners/line_triangulation.py b/limap/runners/line_triangulation.py index e1a0349d..8bb9b8f6 100644 --- a/limap/runners/line_triangulation.py +++ b/limap/runners/line_triangulation.py @@ -1,5 +1,6 @@ import os +import pycolmap from pycolmap import logging from tqdm import tqdm @@ -145,7 +146,7 @@ def line_triangulation(cfg, imagecols, neighbors=None, ranges=None): colmap_model_path = cfg["triangulation"]["use_pointsfm"][ "colmap_folder" ] - reconstruction = pointsfm.PyReadCOLMAP(colmap_model_path) + reconstruction = pycolmap.Reconstruction(colmap_model_path) all_bpt2ds, sfm_points = runners.compute_2d_bipartites_from_colmap( reconstruction, imagecols, all_2d_lines, cfg["structures"]["bpt2d"] ) diff --git a/limap/visualize/__init__.py b/limap/visualize/__init__.py index 3a87bae1..928caa5c 100644 --- a/limap/visualize/__init__.py +++ b/limap/visualize/__init__.py @@ -12,6 +12,7 @@ ) from .vis_utils import ( compute_robust_range_lines, + compute_robust_range_points, draw_segments, vis_vpresult, visualize_line_track, @@ -28,6 +29,7 @@ "open3d_vis_3d_lines", "vis_vpresult", "draw_segments", + "compute_robust_range_points", "compute_robust_range_lines", "visualize_line_track", ] diff --git a/limap/visualize/vis_utils.py b/limap/visualize/vis_utils.py index 142a495a..f6e14a10 100644 --- a/limap/visualize/vis_utils.py +++ b/limap/visualize/vis_utils.py @@ -334,6 +334,27 @@ def compute_robust_range(arr, range_robust=None, k_stretch=2.0): return start_stretched, end_stretched +def compute_robust_range_points(points, range_robust=None, k_stretch=2.0): + if range_robust is None: + range_robust = [0.05, 0.95] + points_array = np.array(points) + x_array = points_array.reshape(-1, 3)[:, 0] + y_array = points_array.reshape(-1, 3)[:, 1] + z_array = points_array.reshape(-1, 3)[:, 2] + + x_start, x_end = compute_robust_range( + x_array, range_robust=range_robust, k_stretch=k_stretch + ) + y_start, y_end = compute_robust_range( + y_array, range_robust=range_robust, k_stretch=k_stretch + ) + z_start, z_end = compute_robust_range( + z_array, range_robust=range_robust, k_stretch=k_stretch + ) + ranges = np.array([[x_start, y_start, z_start], [x_end, y_end, z_end]]) + return ranges + + def compute_robust_range_lines(lines, range_robust=None, k_stretch=2.0): if range_robust is None: range_robust = [0.05, 0.95] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..784ba86d --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +markers = + ci_workflow: mark a test as part of the CI workflow + diff --git a/requirements.txt b/requirements.txt index 96728ceb..3129ccdd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,6 +19,7 @@ imagesize einops ninja yacs +pytest numpy<=1.26.4 # Tag numpy 1.26.4 for Open3D issue (https://github.com/numpy/numpy/issues/26853) open3d==0.18.0 pycolmap>=3.11.1 diff --git a/tests/base/test_linebase.py b/tests/base/test_linebase.py new file mode 100644 index 00000000..68a8d167 --- /dev/null +++ b/tests/base/test_linebase.py @@ -0,0 +1,17 @@ +import numpy as np +import numpy.testing as npt +import pytest + +import limap + + +@pytest.mark.ci_workflow +def test_line2d(): + line = limap.base.Line2d(np.array([0.0, 0.0]), np.array([1.0, 1.0])) + npt.assert_allclose(line.length(), np.sqrt(2), rtol=1e-5, atol=1e-8) + npt.assert_allclose( + line.direction(), + np.array([1.0, 1.0]) / line.length(), + rtol=1e-5, + atol=1e-8, + ) diff --git a/tests/pointsfm/test_colmap_reader.py b/tests/pointsfm/test_colmap_reader.py new file mode 100644 index 00000000..02ff0e71 --- /dev/null +++ b/tests/pointsfm/test_colmap_reader.py @@ -0,0 +1,29 @@ +import numpy.testing as npt +import pycolmap +import pytest + +from limap.pointsfm.colmap_reader import ReadInfos, ReadPointTracks + + +@pytest.mark.ci_workflow +def test_convert_colmap_to_imagecols(tmp_path): + syn_options = pycolmap.SyntheticDatasetOptions() + recon = pycolmap.synthesize_dataset(syn_options) + recon.write(tmp_path) + imagecols = ReadInfos(tmp_path, model_path=".") + assert imagecols.NumCameras() == recon.num_cameras() + assert imagecols.NumImages() == recon.num_images() + npt.assert_allclose( + recon.images[1].cam_from_world.inverse().translation, + imagecols.camimage(1).pose.center(), + rtol=1e-5, + atol=1e-8, + ) + + +@pytest.mark.ci_workflow +def test_read_point_tracks(tmp_path): + syn_options = pycolmap.SyntheticDatasetOptions() + recon = pycolmap.synthesize_dataset(syn_options) + point_tracks = ReadPointTracks(recon) + assert len(point_tracks) == recon.num_points3D() diff --git a/tests/pointsfm/test_model_converter.py b/tests/pointsfm/test_model_converter.py new file mode 100644 index 00000000..8a274acf --- /dev/null +++ b/tests/pointsfm/test_model_converter.py @@ -0,0 +1,13 @@ +import pycolmap +import pytest + +from limap.pointsfm.model_converter import convert_colmap_to_visualsfm + + +@pytest.mark.ci_workflow +def test_convert_colmap_to_visualsfm(tmp_path): + syn_options = pycolmap.SyntheticDatasetOptions() + recon = pycolmap.synthesize_dataset(syn_options) + recon.write(tmp_path) + output_nvm_file = tmp_path / "test_output.nvm" + convert_colmap_to_visualsfm(tmp_path, output_nvm_file) diff --git a/tests/third-party/test_open3d.py b/tests/third-party/test_open3d.py new file mode 100644 index 00000000..d5903ca2 --- /dev/null +++ b/tests/third-party/test_open3d.py @@ -0,0 +1,14 @@ +import numpy as np +import open3d as o3d +import pytest + + +@pytest.mark.ci_workflow +def test_open3d(): + o3d.geometry.LineSet.create_camera_visualization( + 500, + 500, + np.array([[500.0, 0.0, 250.0], [0.0, 500.0, 250.0], [0.0, 0.0, 1.0]]), + np.eye(4), + scale=1.0, + ) diff --git a/tests/third-party/test_pycolmap.py b/tests/third-party/test_pycolmap.py new file mode 100644 index 00000000..39f18bba --- /dev/null +++ b/tests/third-party/test_pycolmap.py @@ -0,0 +1,13 @@ +import pycolmap +import pytest + + +@pytest.mark.ci_workflow +def test_pycolmap(): + syn_options = pycolmap.SyntheticDatasetOptions( + num_cameras=3, num_images=8, num_points3D=100 + ) + recon = pycolmap.synthesize_dataset(syn_options) + assert recon.num_cameras() == 3 + assert recon.num_images() == 8 + assert recon.num_points3D() == 100 diff --git a/visualize_colmap_model.py b/visualize_colmap_model.py new file mode 100644 index 00000000..dcff7dc5 --- /dev/null +++ b/visualize_colmap_model.py @@ -0,0 +1,68 @@ +import numpy as np +import open3d as o3d +import pycolmap + +import limap.pointsfm as pointsfm +import limap.visualize as limapvis + + +def parse_args(): + import argparse + + arg_parser = argparse.ArgumentParser( + description="visualize colmap model using Open3D backend" + ) + arg_parser.add_argument( + "-i", "--input_dir", type=str, required=True, help="input colmap folder" + ) + arg_parser.add_argument( + "--use_robust_ranges", + action="store_true", + help="whether to use computed robust ranges", + ) + arg_parser.add_argument( + "--scale", + type=float, + default=1.0, + help="scaling both the lines and the camera geometry", + ) + arg_parser.add_argument( + "--cam_scale", + type=float, + default=1.0, + help="scale of the camera geometry", + ) + args = arg_parser.parse_args() + return args + + +def vis_colmap_reconstruction(recon: pycolmap.Reconstruction, ranges=None): + vis = o3d.visualization.Visualizer() + vis.create_window(height=1080, width=1920) + points = np.array([point.xyz for _, point in recon.points3D.items()]) + pcd = limapvis.open3d_get_points(points, ranges=ranges) + vis.add_geometry(pcd) + imagecols = pointsfm.convert_colmap_to_imagecols(recon) + camera_set = limapvis.open3d_get_cameras( + imagecols, + ranges=ranges, + scale_cam_geometry=args.scale * args.cam_scale, + scale=args.scale, + ) + vis.add_geometry(camera_set) + vis.run() + vis.destroy_window() + + +def main(args): + recon = pycolmap.Reconstruction(args.input_dir) + ranges = None + if args.use_robust_ranges: + points = np.array([point.xyz for _, point in recon.points3D.items()]) + ranges = limapvis.compute_robust_range_points(points) + vis_colmap_reconstruction(recon, ranges=ranges) + + +if __name__ == "__main__": + args = parse_args() + main(args)