From 2cadd62c843867ea47797c3fbd66e27af8e129ee Mon Sep 17 00:00:00 2001 From: Shaohui Liu Date: Wed, 20 Nov 2024 17:57:14 +0100 Subject: [PATCH] Change all print() to logging.info(). Fix linting issues for B008 and E501 (#94) * fix some B008 * refactor and fix B008. add database to exception * E501 progress * Fix E501 * change print() to logging.info() --- limap/base/depth_reader_base.py | 13 ++- limap/base/functions.py | 32 +++++-- limap/base/p3d_reader_base.py | 6 +- limap/base/unit_test.py | 6 +- limap/estimators/absolute_pose/__init__.py | 43 ++++++--- .../_pl_estimate_absolute_pose.py | 6 +- limap/features/extractors.py | 11 ++- limap/features/models/s2dnet.py | 11 +-- limap/line2d/DeepLSD/deeplsd.py | 10 ++- limap/line2d/GlueStick/extractor.py | 7 +- limap/line2d/GlueStick/matcher.py | 10 ++- limap/line2d/HAWPv3/hawp.py | 10 ++- limap/line2d/L2D2/extractor.py | 10 ++- limap/line2d/L2D2/matcher.py | 7 +- limap/line2d/LBD/extractor.py | 11 ++- limap/line2d/LBD/matcher.py | 7 +- limap/line2d/LSD/lsd.py | 7 +- limap/line2d/LineTR/extractor.py | 7 +- limap/line2d/LineTR/line_transformer.py | 5 +- limap/line2d/LineTR/matcher.py | 7 +- limap/line2d/SOLD2/misc/train_utils.py | 9 +- limap/line2d/SOLD2/model/line_detection.py | 4 +- limap/line2d/SOLD2/model/line_matcher.py | 7 +- limap/line2d/SOLD2/model/loss.py | 4 +- limap/line2d/SOLD2/model/metrics.py | 3 +- limap/line2d/SOLD2/model/model_util.py | 18 ++-- limap/line2d/SOLD2/sold2.py | 14 ++- limap/line2d/SOLD2/sold2_wrapper.py | 3 +- limap/line2d/SOLD2/train.py | 42 ++++----- limap/line2d/TP_LSD/tp_lsd.py | 10 ++- limap/line2d/__init__.py | 2 - limap/line2d/base_detector.py | 89 +++++++++++++------ limap/line2d/base_matcher.py | 29 ++++-- limap/line2d/endpoints/extractor.py | 7 +- limap/line2d/endpoints/matcher.py | 9 +- limap/line2d/line_utils/merge_lines.py | 2 +- limap/line2d/register_matcher.py | 3 +- limap/merging/merging.py | 7 +- .../line_refinement/line_refinement.py | 11 ++- limap/optimize/line_refinement/solve.py | 1 - limap/point2d/superglue/superglue.py | 8 +- limap/point2d/superpoint/main.py | 10 ++- limap/point2d/superpoint/superpoint.py | 5 +- limap/pointsfm/bundler_reader.py | 5 +- limap/pointsfm/colmap_reader.py | 5 +- limap/pointsfm/colmap_sfm.py | 11 ++- limap/pointsfm/database.py | 5 +- limap/pointsfm/functions.py | 4 +- limap/pointsfm/read_write_model.py | 20 +++-- limap/runners/functions.py | 88 +++++++++++------- limap/runners/functions_structures.py | 3 +- limap/runners/line_fitnmerge.py | 80 ++++++++++++----- limap/runners/line_localization.py | 59 ++++++++---- limap/runners/line_triangulation.py | 19 ++-- limap/triangulation/triangulation.py | 6 +- limap/undistortion/undistort.py | 19 ++-- limap/util/evaluation.py | 14 ++- limap/util/io.py | 27 +++--- limap/visualize/trackvis/base.py | 23 +++-- limap/visualize/vis_bipartite.py | 1 - limap/visualize/vis_utils.py | 25 ++++-- limap/vplib/JLinkage/JLinkage.py | 4 +- limap/vplib/base_vp_detector.py | 18 ++-- limap/vplib/progressivex/progressivex.py | 4 +- ruff.toml | 11 +-- runners/cambridge/localization.py | 1 - runners/hypersim/Hypersim.py | 6 +- runners/hypersim/refine_sfm.py | 9 +- runners/inloc/utils.py | 3 +- runners/pointline_association.py | 9 +- runners/rome16k/statistics.py | 3 +- runners/rome16k/triangulation.py | 3 +- runners/tests/line2d.py | 4 +- runners/tests/localization.py | 9 +- scripts/eval_hypersim.py | 6 +- scripts/eval_tnt.py | 9 +- scripts/format/black.sh | 25 ++++++ scripts/tnt_align.py | 10 ++- scripts/tnt_colmap_runner.py | 9 +- setup.py | 3 +- visualize_3d_lines.py | 3 +- 81 files changed, 734 insertions(+), 352 deletions(-) create mode 100755 scripts/format/black.sh diff --git a/limap/base/depth_reader_base.py b/limap/base/depth_reader_base.py index 651d470b..313319da 100644 --- a/limap/base/depth_reader_base.py +++ b/limap/base/depth_reader_base.py @@ -3,7 +3,8 @@ class BaseDepthReader: """ - Base class for the depth reader storing the filename and potentially other information + Base class for the depth reader storing the filename and \ + potentially other information """ def __init__(self, filename): @@ -16,7 +17,8 @@ def read(self, filename): Args: filename (str): The filename of the depth image Returns: - depth (:class:`np.array` of shape (H, W)): The array for the depth map + depth (:class:`np.array` of shape (H, W)): \ + The array for the depth map """ raise NotImplementedError @@ -25,9 +27,12 @@ def read_depth(self, img_hw=None): Read depth using the read(self, filename) function Args: - img_hw (pair of int, optional): The height and width for the read depth. By default we keep the original resolution of the file + img_hw (pair of int, optional): \ + The height and width for the read depth. \ + By default we keep the original resolution of the file Returns: - depth (:class:`np.array` of shape (H, W)): The array for the depth map + depth (:class:`np.array` of shape (H, W)): \ + The array for the depth map """ depth = self.read(self.filename) if img_hw is not None and ( diff --git a/limap/base/functions.py b/limap/base/functions.py index ce50bc56..fd86c062 100644 --- a/limap/base/functions.py +++ b/limap/base/functions.py @@ -3,13 +3,18 @@ def get_all_lines_2d(all_2d_segs): """ - Convert :class:`np.array` representations of 2D line segments to dict of :class:`~limap.base.Line2d`. + Convert :class:`np.array` representations of 2D line segments \ + to dict of :class:`~limap.base.Line2d`. Args: - all_2d_segs (dict[int -> :class:`np.array`]): Map image IDs to :class:`np.array` of shape (N, 4), each row (4 numbers) is concatenated by the start and end of a 2D line segment. + all_2d_segs (dict[int -> :class:`np.array`]): \ + Map image IDs to :class:`np.array` of shape (N, 4), \ + each row (4 numbers) is concatenated by the start and end \ + of a 2D line segment. Returns: - dict[int -> list[:class:`~limap.base.Line2d`]]: Map image IDs to list of :class:`~limap.base.Line2d`. + dict[int -> list[:class:`~limap.base.Line2d`]]: \ + Map image IDs to list of :class:`~limap.base.Line2d`. """ all_lines_2d = {} for img_id in all_2d_segs: @@ -21,13 +26,18 @@ def get_all_lines_2d(all_2d_segs): def get_all_lines_3d(all_3d_segs): """ - Convert :class:`np.array` representations of 3D line segments to dict of :class:`~limap.base.Line3d`. + Convert :class:`np.array` representations of 3D line segments \ + to dict of :class:`~limap.base.Line3d`. Args: - all_3d_segs (dict[int -> :class:`np.array`]): Map image IDs to :class:`np.array` of shape (N, 2, 3), each 2*3 matrix is stacked from the two endpoints of a 3D line segment. + all_3d_segs (dict[int -> :class:`np.array`]): \ + Map image IDs to :class:`np.array` of shape (N, 2, 3), \ + each 2*3 matrix is stacked from the two endpoints \ + of a 3D line segment. Returns: - dict[int -> list[:class:`~limap.base.Line3d`]]: Map image IDs to list of :class:`~limap.base.Line3d`. + dict[int -> list[:class:`~limap.base.Line3d`]]: \ + Map image IDs to list of :class:`~limap.base.Line3d`. """ all_lines_3d = {} for img_id, segs3d in all_3d_segs.items(): @@ -37,14 +47,18 @@ def get_all_lines_3d(all_3d_segs): def get_invert_idmap_from_linetracks(all_lines_2d, linetracks): """ - Get the mapping from a 2D line segment (identified by an image and its line ID) to the index of its associated linetrack. + Get the mapping from a 2D line segment (identified by an image and \ + its line ID) to the index of its associated linetrack. Args: - all_lines_2d (dict[int -> list[:class:`~limap.base.Line2d`]]): Map image IDs to the list of 2D line segments in each image. + all_lines_2d (dict[int -> list[:class:`~limap.base.Line2d`]]): \ + Map image IDs to the list of 2D line segments in each image. linetracks (list[:class:`~limap.base.LineTrack`]): All line tracks. Returns: - dict[int -> list[int]]: Map image ID to list of the associated line track indices for each 2D line, -1 if not associated to any track. + dict[int -> list[int]]: Map image ID to list of the associated \ + line track indices for each 2D line, \ + -1 if not associated to any track. """ map = {} for img_id in all_lines_2d: diff --git a/limap/base/p3d_reader_base.py b/limap/base/p3d_reader_base.py index 7ac89841..d353b7aa 100644 --- a/limap/base/p3d_reader_base.py +++ b/limap/base/p3d_reader_base.py @@ -9,7 +9,8 @@ def read(self, filename): Args: filename (str): The filename of the depth image Returns: - point cloud (:class:`np.array` of shape (N, 3)): The array for the 3D points + point cloud (:class:`np.array` of shape (N, 3)): \ + The array for the 3D points """ raise NotImplementedError @@ -18,7 +19,8 @@ def read_p3ds(self): Read a point cloud using the read(self, filename) function Returns: - point cloud (:class:`np.array` of shape (N, 3)): The array for the 3D points + point cloud (:class:`np.array` of shape (N, 3)): \ + The array for the 3D points """ p3ds = self.read(self.filename) return p3ds diff --git a/limap/base/unit_test.py b/limap/base/unit_test.py index 81235345..84a9c57c 100644 --- a/limap/base/unit_test.py +++ b/limap/base/unit_test.py @@ -1,3 +1,5 @@ +import logging + import _limap._base as _base import numpy as np @@ -26,7 +28,7 @@ def report_error(imagecols_pred, imagecols): ) error = np.abs(error) camera_errors.append(error) - print("camera_errors", np.array(camera_errors).mean(0)) + logging.info("camera_errors", np.array(camera_errors).mean(0)) # images pose_errors = [] @@ -40,4 +42,4 @@ def report_error(imagecols_pred, imagecols): ) T_error = np.sqrt(np.sum(T_error**2)) pose_errors.append(np.array([R_error, T_error])) - print("pose_error: (R, T)", np.array(pose_errors).mean(0)) + logging.info("pose_error: (R, T)", np.array(pose_errors).mean(0)) diff --git a/limap/estimators/absolute_pose/__init__.py b/limap/estimators/absolute_pose/__init__.py index b5dee5af..87221a0a 100644 --- a/limap/estimators/absolute_pose/__init__.py +++ b/limap/estimators/absolute_pose/__init__.py @@ -17,25 +17,42 @@ def pl_estimate_absolute_pose( logger=None, ): """ - Estimate absolute camera pose of a image from matched 2D-3D line and point correspondences. + Estimate absolute camera pose of a image from matched 2D-3D \ + line and point correspondences. Args: - cfg (dict): Localization config, fields refer to "localization" section in :file:`cfgs/localization/default.yaml` + cfg (dict): Localization config, \ + fields refer to "localization" section \ + in :file:`cfgs/localization/default.yaml` l3ds (list[:class:`limap.base.Line3d`]): Matched 3D line segments - l3d_ids (list[int]): Indices into `l3ds` for match of each :class:`limap.base.Line3d` in `l2ds`, same length of `l2ds` - l2ds (list[:class:`limap.base.Line2d`]): Matched 2d lines, same length of `l3d_ids` - p3ds (list[:class:`np.array`]): Matched 3D points, same length of `p2ds` - p2ds (list[:class:`np.array`]): Matched 2D points, same length of `p3ds` + l3d_ids (list[int]): Indices into `l3ds` for match of \ + each :class:`limap.base.Line3d` in `l2ds`, same length of `l2ds` + l2ds (list[:class:`limap.base.Line2d`]): \ + Matched 2d lines, same length of `l3d_ids` + p3ds (list[:class:`np.array`]): \ + Matched 3D points, same length of `p2ds` + p2ds (list[:class:`np.array`]): \ + Matched 2D points, same length of `p3ds` camera (:class:`limap.base.Camera`): Camera of the query image - campose (:class:`limap.base.CameraPose`, optional): Initial camera pose, only useful for pose refinement (when ``cfg["ransac"]["method"]`` is :py:obj:`None`) - inliers_line (list[int], optional): Indices of line inliers, only useful for pose refinement - inliers_point (list[int], optional): Indices of point inliers, only useful for pose refinement - jointloc_cfg (dict, optional): Config for joint optimization, fields refer to :class:`limap.optimize.LineLocConfig`, pass :py:obj:`None` for default - silent (bool, optional): Turn off to print logs during Ceres optimization - logger (:class:`logging.Logger`): Logger to print logs for information + campose (:class:`limap.base.CameraPose`, optional): \ + Initial camera pose, only useful for pose refinement \ + (when ``cfg["ransac"]["method"]`` is :py:obj:`None`) + inliers_line (list[int], optional): \ + Indices of line inliers, only useful for pose refinement + inliers_point (list[int], optional): \ + Indices of point inliers, only useful for pose refinement + jointloc_cfg (dict, optional): Config for joint optimization, \ + fields refer to :class:`limap.optimize.LineLocConfig`, \ + pass :py:obj:`None` for default + silent (bool, optional): \ + Turn off to print logs during Ceres optimization + logger (:class:`logging.Logger`): \ + Logger to print logs for information Returns: - tuple[:class:`limap.base.CameraPose`, :class:`limap.estimators.RansacStatistics`]: Estimated pose and ransac statistics. + tuple[:class:`limap.base.CameraPose`, \ + :class:`limap.estimators.RansacStatistics`]: \ + Estimated pose and ransac statistics. """ return _pl_estimate_absolute_pose( cfg, diff --git a/limap/estimators/absolute_pose/_pl_estimate_absolute_pose.py b/limap/estimators/absolute_pose/_pl_estimate_absolute_pose.py index dd962d9b..b5522aa9 100644 --- a/limap/estimators/absolute_pose/_pl_estimate_absolute_pose.py +++ b/limap/estimators/absolute_pose/_pl_estimate_absolute_pose.py @@ -52,7 +52,8 @@ def _pl_estimate_absolute_pose( p3ds = np.array(p3ds)[inliers_point] if logger: logger.info( - f"{len(p2ds)} inliers reserved from {original_len} point matches" + f"{len(p2ds)} inliers reserved from \ + {original_len} point matches" ) if inliers_line is not None: @@ -61,7 +62,8 @@ def _pl_estimate_absolute_pose( l2ds = np.array(l2ds)[inliers_line] if logger: logger.info( - f"{len(l3d_ids)} inliers reserved from {original_len} line matches" + f"{len(l3d_ids)} inliers reserved from \ + {original_len} line matches" ) jointloc = _optimize.solve_jointloc( diff --git a/limap/features/extractors.py b/limap/features/extractors.py index 05e94468..6989f07e 100644 --- a/limap/features/extractors.py +++ b/limap/features/extractors.py @@ -1,5 +1,6 @@ # [NOTE] modified from the pixel-perfect-sfm project +import logging import sys import time @@ -63,7 +64,8 @@ def extract_featuremaps(self, image_batch: torch.Tensor) -> list: image_batch: [BxHxWxC] Tensor. Returns: - List of self.num_levels featuremaps as torch.Tensor[HxWxC] on device. + List of self.num_levels featuremaps as \ + torch.Tensor[HxWxC] on device. """ raise NotImplementedError() @@ -130,14 +132,13 @@ def extract_featuremaps(self, image_batch: torch.Tensor) -> list: .permute(2, 0, 1) .unsqueeze(0) ) - print(res.shape, image_batch.shape) + logging.info(res.shape, image_batch.shape) return [res] def adapt_image(self, pil_img: PIL.Image) -> torch.Tensor: - print("HELLO") t = time.time() res = to_grayscale(pil_img).unsqueeze(0) - print(time.time() - t) + logging.info(time.time() - t) return res @@ -159,7 +160,6 @@ def extract_featuremaps(self, image_batch: torch.Tensor) -> list: maps[i] = maps[i][:, :channels] early = maps[0] middle = maps[1] - # combined = early + nn.Upsample(size = early.shape[2:], mode="bilinear", align_corners=True)(middle) return [early, middle] def adapt_image(self, pil_img: PIL.Image) -> torch.Tensor: @@ -178,7 +178,6 @@ def extract_featuremaps(self, image_batch: torch.Tensor) -> list: def adapt_image(self, pil_img: PIL.Image) -> torch.Tensor: return tvf.to_tensor(pil_img).unsqueeze(0) - # return torch.from_numpy(np.asarray(pil_img) / 255.0).permute(2,0,1).unsqueeze(0) class GrayscaleExtractor(Extractor): diff --git a/limap/features/models/s2dnet.py b/limap/features/models/s2dnet.py index c33ff819..b712a838 100644 --- a/limap/features/models/s2dnet.py +++ b/limap/features/models/s2dnet.py @@ -58,7 +58,7 @@ def print_gpu_memory(): a = torch.cuda.memory_allocated(0) f = r - a # free inside reserved - print(np.array([t, r, a, f]) / 2**30) + logging.info(np.array([t, r, a, f]) / 2**30) class AdapLayers(nn.Module): @@ -73,7 +73,7 @@ def __init__(self, hypercolumn_layers: List[str], output_dim: int = 128): super().__init__() self.layers = [] channel_sizes = [vgg16_layers[name] for name in hypercolumn_layers] - print(channel_sizes) + logging.info(channel_sizes) for i, ll in enumerate(channel_sizes): layer = nn.Sequential( nn.Conv2d(ll, 64, kernel_size=1, stride=1, padding=0), @@ -115,7 +115,7 @@ def _init(self, conf): layers = list(vgg16.features.children())[:num_layers] self.encoder = nn.ModuleList(layers) - print(self.encoder) + logging.info(self.encoder) self.scales = [] current_scale = 0 for i, layer in enumerate(layers): @@ -138,7 +138,8 @@ def _init(self, conf): self.load_state_dict(state_dict, strict=False) def download_s2dnet_model(self, path): - # TODO: not supporting global weight_path now. Downloading to current directory. + # TODO: not supporting global weight_path now. + # Downloading to current directory. import subprocess if not os.path.exists(os.path.dirname(path)): @@ -147,7 +148,7 @@ def download_s2dnet_model(self, path): "https://www.dropbox.com/s/hnv51iwu4hn82rj/s2dnet_weights.pth?dl=0" ) cmd = ["wget", link, "-O", path] - print("Downloading S2DNet model...") + logging.info("Downloading S2DNet model...") subprocess.run(cmd, check=True) def _forward(self, data): diff --git a/limap/line2d/DeepLSD/deeplsd.py b/limap/line2d/DeepLSD/deeplsd.py index fffa5857..5bd09638 100644 --- a/limap/line2d/DeepLSD/deeplsd.py +++ b/limap/line2d/DeepLSD/deeplsd.py @@ -1,14 +1,18 @@ +import logging import os import numpy as np import torch from deeplsd.models.deeplsd_inference import DeepLSD -from ..base_detector import BaseDetector, BaseDetectorOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) class DeepLSDDetector(BaseDetector): - def __init__(self, options=BaseDetectorOptions()): + def __init__(self, options=DefaultDetectorOptions): super().__init__(options) conf = { @@ -41,7 +45,7 @@ def download_model(self, path): os.makedirs(os.path.dirname(path)) link = "https://cvg-data.inf.ethz.ch/DeepLSD/deeplsd_md.tar" cmd = ["wget", link, "-O", path] - print("Downloading DeepLSD model...") + logging.info("Downloading DeepLSD model...") subprocess.run(cmd, check=True) def get_module_name(self): diff --git a/limap/line2d/GlueStick/extractor.py b/limap/line2d/GlueStick/extractor.py index 112af1d6..3309da48 100644 --- a/limap/line2d/GlueStick/extractor.py +++ b/limap/line2d/GlueStick/extractor.py @@ -8,11 +8,14 @@ import limap.util.io as limapio from limap.point2d.superpoint.superpoint import SuperPoint, sample_descriptors -from ..base_detector import BaseDetector, BaseDetectorOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) class WireframeExtractor(BaseDetector): - def __init__(self, options=BaseDetectorOptions(), device=None): + def __init__(self, options=DefaultDetectorOptions, device=None): super().__init__(options) self.device = "cuda" if device is None else device self.sp = ( diff --git a/limap/line2d/GlueStick/matcher.py b/limap/line2d/GlueStick/matcher.py index 0ba132d8..eac0e9cf 100644 --- a/limap/line2d/GlueStick/matcher.py +++ b/limap/line2d/GlueStick/matcher.py @@ -1,14 +1,18 @@ +import logging import os import numpy as np import torch from gluestick.models.gluestick import GlueStick -from ..base_matcher import BaseMatcher, BaseMatcherOptions +from ..base_matcher import ( + BaseMatcher, + DefaultMatcherOptions, +) class GlueStickMatcher(BaseMatcher): - def __init__(self, extractor, options=BaseMatcherOptions(), device=None): + def __init__(self, extractor, options=DefaultMatcherOptions, device=None): super().__init__(extractor, options) self.device = "cuda" if device is None else device self.gs = GlueStick({}).eval().to(self.device) @@ -37,7 +41,7 @@ def download_model(self, path): os.makedirs(os.path.dirname(path)) link = "https://github.com/cvg/GlueStick/releases/download/v0.1_arxiv/checkpoint_GlueStick_MD.tar" cmd = ["wget", link, "-O", path] - print("Downloading GlueStick model...") + logging.info("Downloading GlueStick model...") subprocess.run(cmd, check=True) def get_module_name(self): diff --git a/limap/line2d/HAWPv3/hawp.py b/limap/line2d/HAWPv3/hawp.py index c27dc421..ea9aefa5 100644 --- a/limap/line2d/HAWPv3/hawp.py +++ b/limap/line2d/HAWPv3/hawp.py @@ -1,3 +1,4 @@ +import logging import os import cv2 @@ -6,11 +7,14 @@ from hawp.fsl.config import cfg as model_config from hawp.ssl.models import MODELS -from ..base_detector import BaseDetector, BaseDetectorOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) class HAWPv3Detector(BaseDetector): - def __init__(self, options=BaseDetectorOptions()): + def __init__(self, options=DefaultDetectorOptions): super().__init__(options) # Load the HAWPv3 model if self.weight_path is None: @@ -42,7 +46,7 @@ def download_model(self, path): os.makedirs(os.path.dirname(path)) link = "https://github.com/cherubicXN/hawp-torchhub/releases/download/HAWPv3/hawpv3-fdc5487a.pth" cmd = ["wget", link, "-O", path] - print("Downloading HAWPv3 model...") + logging.info("Downloading HAWPv3 model...") subprocess.run(cmd, check=True) def get_module_name(self): diff --git a/limap/line2d/L2D2/extractor.py b/limap/line2d/L2D2/extractor.py index 4820d9e8..8d122351 100644 --- a/limap/line2d/L2D2/extractor.py +++ b/limap/line2d/L2D2/extractor.py @@ -1,3 +1,4 @@ +import logging import os import cv2 @@ -6,11 +7,14 @@ import limap.util.io as limapio -from ..base_detector import BaseDetector, BaseDetectorOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) class L2D2Extractor(BaseDetector): - def __init__(self, options=BaseDetectorOptions(), device=None): + def __init__(self, options=DefaultDetectorOptions, device=None): super().__init__(options) self.mini_batch = 20 self.device = "cuda" if device is None else device @@ -40,7 +44,7 @@ def download_model(self, path): os.makedirs(os.path.dirname(path)) link = "https://github.com/hichem-abdellali/L2D2/blob/main/IN_OUT_DATA/INPUT_NETWEIGHT/checkpoint_line_descriptor.th?raw=true" cmd = ["wget", link, "-O", path] - print("Downloading L2D2 model...") + logging.info("Downloading L2D2 model...") subprocess.run(cmd, check=True) def get_module_name(self): diff --git a/limap/line2d/L2D2/matcher.py b/limap/line2d/L2D2/matcher.py index ae304eb9..86434fb5 100644 --- a/limap/line2d/L2D2/matcher.py +++ b/limap/line2d/L2D2/matcher.py @@ -1,10 +1,13 @@ import numpy as np -from ..base_matcher import BaseMatcher, BaseMatcherOptions +from ..base_matcher import ( + BaseMatcher, + DefaultMatcherOptions, +) class L2D2Matcher(BaseMatcher): - def __init__(self, extractor, options=BaseMatcherOptions()): + def __init__(self, extractor, options=DefaultMatcherOptions): super().__init__(extractor, options) def get_module_name(self): diff --git a/limap/line2d/LBD/extractor.py b/limap/line2d/LBD/extractor.py index 58abe9fc..b39f2d5a 100644 --- a/limap/line2d/LBD/extractor.py +++ b/limap/line2d/LBD/extractor.py @@ -7,12 +7,17 @@ import limap.util.io as limapio -from ..base_detector import BaseDetector, BaseDetectorOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) def process_pyramid( - img, detector, n_levels=5, level_scale=np.sqrt(2), presmooth=True + img, detector, n_levels=5, level_scale=None, presmooth=True ): + if level_scale is None: + level_scale = np.sqrt(2) octave_img = img.copy() pre_sigma2 = 0 cur_sigma2 = 1.0 @@ -56,7 +61,7 @@ def to_multiscale_lines(lines): class LBDExtractor(BaseDetector): - def __init__(self, options=BaseDetectorOptions()): + def __init__(self, options=DefaultDetectorOptions): super().__init__(options) def get_module_name(self): diff --git a/limap/line2d/LBD/matcher.py b/limap/line2d/LBD/matcher.py index ffdd5b81..0dd68f61 100644 --- a/limap/line2d/LBD/matcher.py +++ b/limap/line2d/LBD/matcher.py @@ -1,11 +1,14 @@ import numpy as np import pytlbd -from ..base_matcher import BaseMatcher, BaseMatcherOptions +from ..base_matcher import ( + BaseMatcher, + DefaultMatcherOptions, +) class LBDMatcher(BaseMatcher): - def __init__(self, extractor, options=BaseMatcherOptions()): + def __init__(self, extractor, options=DefaultMatcherOptions): super().__init__(extractor, options) def get_module_name(self): diff --git a/limap/line2d/LSD/lsd.py b/limap/line2d/LSD/lsd.py index 286f08a7..08ae9db6 100644 --- a/limap/line2d/LSD/lsd.py +++ b/limap/line2d/LSD/lsd.py @@ -1,10 +1,13 @@ import pytlsd -from ..base_detector import BaseDetector, BaseDetectorOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) class LSDDetector(BaseDetector): - def __init__(self, options=BaseDetectorOptions()): + def __init__(self, options=DefaultDetectorOptions): super().__init__(options) def get_module_name(self): diff --git a/limap/line2d/LineTR/extractor.py b/limap/line2d/LineTR/extractor.py index f3f495c8..10cae8a2 100644 --- a/limap/line2d/LineTR/extractor.py +++ b/limap/line2d/LineTR/extractor.py @@ -7,12 +7,15 @@ import limap.util.io as limapio from limap.point2d.superpoint.superpoint import SuperPoint -from ..base_detector import BaseDetector, BaseDetectorOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) from .line_transformer import LineTransformer class LineTRExtractor(BaseDetector): - def __init__(self, options=BaseDetectorOptions(), device=None): + def __init__(self, options=DefaultDetectorOptions, device=None): super().__init__(options) self.device = "cuda" if device is None else device self.sp = SuperPoint({}).eval().to(self.device) diff --git a/limap/line2d/LineTR/line_transformer.py b/limap/line2d/LineTR/line_transformer.py index 348fd05c..e733b247 100755 --- a/limap/line2d/LineTR/line_transformer.py +++ b/limap/line2d/LineTR/line_transformer.py @@ -1,3 +1,4 @@ +import logging from copy import deepcopy from pathlib import Path @@ -328,7 +329,7 @@ def __init__(self, config): if not path.is_file(): self.download_model(path) self.load_state_dict(torch.load(str(path))) - print("Loaded Line-Transformer model") + logging.info("Loaded Line-Transformer model") def download_model(self, path): import subprocess @@ -337,7 +338,7 @@ def download_model(self, path): path.parent.mkdir(parents=True, exist_ok=True) link = "https://github.com/yosungho/LineTR/blob/main/models/weights/LineTR_weight.pth?raw=true" cmd = ["wget", link, "-O", str(path)] - print("Downloading LineTR model...") + logging.info("Downloading LineTR model...") subprocess.run(cmd, check=True) def forward(self, data): diff --git a/limap/line2d/LineTR/matcher.py b/limap/line2d/LineTR/matcher.py index a0a3e8c5..f2dc96b5 100644 --- a/limap/line2d/LineTR/matcher.py +++ b/limap/line2d/LineTR/matcher.py @@ -1,6 +1,9 @@ import numpy as np -from ..base_matcher import BaseMatcher, BaseMatcherOptions +from ..base_matcher import ( + BaseMatcher, + DefaultMatcherOptions, +) from .line_process import get_dist_matrix from .line_transformer import LineTransformer from .nn_matcher import nn_matcher_distmat @@ -8,7 +11,7 @@ class LineTRMatcher(BaseMatcher): def __init__( - self, extractor, options=BaseMatcherOptions(), topk=0, device=None + self, extractor, options=DefaultMatcherOptions, topk=0, device=None ): super().__init__(extractor, options) self.device = "cuda" if device is None else device diff --git a/limap/line2d/SOLD2/misc/train_utils.py b/limap/line2d/SOLD2/misc/train_utils.py index 6129759b..470f0a8e 100644 --- a/limap/line2d/SOLD2/misc/train_utils.py +++ b/limap/line2d/SOLD2/misc/train_utils.py @@ -2,6 +2,7 @@ This file contains some useful functions for train / val. """ +import logging import os import numpy as np @@ -21,10 +22,10 @@ def convert_image(input_tensor, axis): ###################### ## checkpoint utils ## ###################### -def get_latest_checkpoint( - checkpoint_root, checkpoint_name, device=torch.device("cuda") -): +def get_latest_checkpoint(checkpoint_root, checkpoint_name, device=None): """Get the latest checkpoint or by filename.""" + if device is None: + device = torch.device("cuda") # Load specific checkpoint if checkpoint_name is not None: checkpoint = torch.load( @@ -59,7 +60,7 @@ def remove_old_checkpoints(checkpoint_root, max_ckpt=15): for _ in remove_list: full_name = os.path.join(checkpoint_root, _) os.remove(full_name) - print("[Debug] Remove outdated checkpoint %s" % (full_name)) + logging.info("[Debug] Remove outdated checkpoint %s" % (full_name)) ################ diff --git a/limap/line2d/SOLD2/model/line_detection.py b/limap/line2d/SOLD2/model/line_detection.py index 1c30796f..53d4cd0e 100644 --- a/limap/line2d/SOLD2/model/line_detection.py +++ b/limap/line2d/SOLD2/model/line_detection.py @@ -92,8 +92,10 @@ def convert_inputs(self, inputs, device): return outputs - def detect(self, junctions, heatmap, device=torch.device("cpu")): + def detect(self, junctions, heatmap, device=None): """Main function performing line segment detection.""" + if device is None: + device = torch.device("cpu") # Convert inputs to torch tensor junctions = self.convert_inputs(junctions, device=device) heatmap = self.convert_inputs(heatmap, device=device) diff --git a/limap/line2d/SOLD2/model/line_matcher.py b/limap/line2d/SOLD2/model/line_matcher.py index 4d9cb457..f4982531 100644 --- a/limap/line2d/SOLD2/model/line_matcher.py +++ b/limap/line2d/SOLD2/model/line_matcher.py @@ -2,6 +2,7 @@ Implements the full pipeline from raw images to line matches. """ +import logging import time import cv2 @@ -60,9 +61,9 @@ def __init__( # Print some debug messages # for key, val in line_detector_cfg.items(): - # print(f"[Debug] {key}: {val}") - # print("[Debug] detect_thresh: %f" % (line_detector_cfg["detect_thresh"])) - # print("[Debug] num_samples: %d" % (line_detector_cfg["num_samples"])) + # logging.info(f"[Debug] {key}: {val}") + # logging.info("[Debug] detect_thresh: %f" % (line_detector_cfg["detect_thresh"])) + # logging.info("[Debug] num_samples: %d" % (line_detector_cfg["num_samples"])) # Perform line detection and descriptor inference on a single image def line_detection( diff --git a/limap/line2d/SOLD2/model/loss.py b/limap/line2d/SOLD2/model/loss.py index e1857b9d..0cde610b 100644 --- a/limap/line2d/SOLD2/model/loss.py +++ b/limap/line2d/SOLD2/model/loss.py @@ -14,8 +14,10 @@ ) -def get_loss_and_weights(model_cfg, device=torch.device("cuda")): +def get_loss_and_weights(model_cfg, device=None): """Get loss functions and either static or dynamic weighting.""" + if device is None: + device = torch.device("cuda") # Get the global weighting policy w_policy = model_cfg.get("weighting_policy", "static") if w_policy not in ["static", "dynamic"]: diff --git a/limap/line2d/SOLD2/model/metrics.py b/limap/line2d/SOLD2/model/metrics.py index d59649a9..c8c5fa0b 100644 --- a/limap/line2d/SOLD2/model/metrics.py +++ b/limap/line2d/SOLD2/model/metrics.py @@ -2,6 +2,8 @@ This file implements the evaluation metrics. """ +import logging + import numpy as np import torch import torch.nn.functional as F @@ -499,7 +501,6 @@ def super_nms(prob_predictions, dist_thresh, prob_thresh=0.01, top_k=0): im_w = prob_predictions.shape[2] output_lst = [] for i in range(prob_predictions.shape[0]): - # print(i) prob_pred = prob_predictions[i, ...] # Filter the points using prob_thresh coord = np.where(prob_pred >= prob_thresh) # HW format diff --git a/limap/line2d/SOLD2/model/model_util.py b/limap/line2d/SOLD2/model/model_util.py index 5910756a..b638219a 100644 --- a/limap/line2d/SOLD2/model/model_util.py +++ b/limap/line2d/SOLD2/model/model_util.py @@ -1,3 +1,5 @@ +import logging + import torch.nn as nn import torch.nn.init as init @@ -15,7 +17,7 @@ def get_model(model_cfg=None, loss_weights=None, mode="train", printing=False): # List the supported options here if printing: - print("\n\n\t--------Initializing model----------") + logging.info("\n\n\t--------Initializing model----------") supported_arch = ["simple"] if model_cfg["model_architecture"] not in supported_arch: raise ValueError( @@ -35,7 +37,7 @@ def get_model(model_cfg=None, loss_weights=None, mode="train", printing=False): for param_name, param in loss_weights.items(): if isinstance(param, nn.Parameter): if printing: - print( + logging.info( "\t [Debug] Adding %s with value %f to model" % (param_name, param.item()) ) @@ -47,11 +49,13 @@ def get_model(model_cfg=None, loss_weights=None, mode="train", printing=False): # Display some summary info. if printing: - print("\tModel architecture: %s" % model_cfg["model_architecture"]) - print("\tBackbone: %s" % model_cfg["backbone"]) - print("\tJunction decoder: %s" % model_cfg["junction_decoder"]) - print("\tHeatmap decoder: %s" % model_cfg["heatmap_decoder"]) - print("\t-------------------------------------") + logging.info( + "\tModel architecture: %s" % model_cfg["model_architecture"] + ) + logging.info("\tBackbone: %s" % model_cfg["backbone"]) + logging.info("\tJunction decoder: %s" % model_cfg["junction_decoder"]) + logging.info("\tHeatmap decoder: %s" % model_cfg["heatmap_decoder"]) + logging.info("\t-------------------------------------") return model diff --git a/limap/line2d/SOLD2/sold2.py b/limap/line2d/SOLD2/sold2.py index 0ab77195..1b4de60a 100644 --- a/limap/line2d/SOLD2/sold2.py +++ b/limap/line2d/SOLD2/sold2.py @@ -3,13 +3,19 @@ import limap.util.io as limapio -from ..base_detector import BaseDetector, BaseDetectorOptions -from ..base_matcher import BaseMatcher, BaseMatcherOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) +from ..base_matcher import ( + BaseMatcher, + DefaultMatcherOptions, +) from .sold2_wrapper import SOLD2LineDetector class SOLD2Detector(BaseDetector): - def __init__(self, options=BaseDetectorOptions()): + def __init__(self, options=DefaultDetectorOptions): super().__init__(options) self.detector = SOLD2LineDetector(weight_path=self.weight_path) @@ -93,7 +99,7 @@ def extract_heatmaps_all_images(self, folder, imagecols, skip_exists=False): class SOLD2Matcher(BaseMatcher): - def __init__(self, extractor, options=BaseMatcherOptions()): + def __init__(self, extractor, options=DefaultMatcherOptions): super().__init__(extractor, options) assert self.extractor.get_module_name() == "sold2" self.detector = SOLD2LineDetector(weight_path=self.weight_path) diff --git a/limap/line2d/SOLD2/sold2_wrapper.py b/limap/line2d/SOLD2/sold2_wrapper.py index a46d38d2..648a6733 100644 --- a/limap/line2d/SOLD2/sold2_wrapper.py +++ b/limap/line2d/SOLD2/sold2_wrapper.py @@ -1,3 +1,4 @@ +import logging import os import subprocess @@ -40,7 +41,7 @@ def initialize_line_matcher(self): os.makedirs(os.path.dirname(self.ckpt_path)) link = "https://cvg-data.inf.ethz.ch/SOLD2/sold2_wireframe.tar" cmd = ["wget", link, "-O", self.ckpt_path] - print("Downloading SOLD2 model...") + logging.info("Downloading SOLD2 model...") subprocess.run(cmd, check=True) self.line_matcher = LineMatcher( self.cfg["model_cfg"], diff --git a/limap/line2d/SOLD2/train.py b/limap/line2d/SOLD2/train.py index 87c4e388..522868ef 100644 --- a/limap/line2d/SOLD2/train.py +++ b/limap/line2d/SOLD2/train.py @@ -2,6 +2,8 @@ This file implements the training process and all the summaries """ +import logging + import cv2 import numpy as np import torch @@ -70,7 +72,7 @@ def restore_weights(model, state_dict, strict=True): # test_cfg = model_cfg["test"] # # # Create train and test dataset -# print("\t Initializing dataset...") +# logging.info("\t Initializing dataset...") # train_dataset, train_collate_fn = get_dataset("train", dataset_cfg) # test_dataset, test_collate_fn = get_dataset("test", dataset_cfg) # @@ -85,7 +87,7 @@ def restore_weights(model, state_dict, strict=True): # num_workers=test_cfg.get("num_workers", 1), # shuffle=False, pin_memory=False, # collate_fn=test_collate_fn) -# print("\t Successfully intialized dataloaders.") +# logging.info("\t Successfully intialized dataloaders.") # # # # Get the loss function and weight first @@ -121,13 +123,13 @@ def restore_weights(model, state_dict, strict=True): # model = get_model(model_cfg, loss_weights) # # Optionally get the pretrained wieghts # if args.pretrained: -# print("\t [Debug] Loading pretrained weights...") +# logging.info("\t [Debug] Loading pretrained weights...") # checkpoint = get_latest_checkpoint(args.pretrained_path, # args.checkpoint_name) # # If auto weighting restore from non-auto weighting # model = restore_weights(model, checkpoint["model_state_dict"], # strict=False) -# print("\t [Debug] Finished loading pretrained weights!") +# logging.info("\t [Debug] Finished loading pretrained weights!") # # model = model.cuda() # optimizer = torch.optim.Adam( @@ -142,7 +144,7 @@ def restore_weights(model, state_dict, strict=True): # optimizer=optimizer) # start_epoch = 0 # -# print("\t Successfully initialized model") +# logging.info("\t Successfully initialized model") # # # Define the total loss # policy = model_cfg.get("weighting_policy", "static") @@ -168,7 +170,7 @@ def restore_weights(model, state_dict, strict=True): # writer.add_scalar("LR/lr", current_lr, epoch) # # # Train for one epochs -# print("\n\n================== Training ====================") +# logging.info("\n\n================== Training ====================") # train_single_epoch( # model=model, # model_cfg=model_cfg, @@ -180,7 +182,7 @@ def restore_weights(model, state_dict, strict=True): # epoch=epoch) # # # Do the validation -# print("\n\n================== Validation ==================") +# logging.info("\n\n================== Validation ==================") # validate( # model=model, # model_cfg=model_cfg, @@ -197,7 +199,7 @@ def restore_weights(model, state_dict, strict=True): # # Save checkpoints # file_name = os.path.join(output_path, # "checkpoint-epoch%03d-end.tar"%(epoch)) -# print("[Info] Saving checkpoint %s ..." % file_name) +# logging.info("[Info] Saving checkpoint %s ..." % file_name) # save_dict = { # "epoch": epoch, # "model_state_dict": model.state_dict(), @@ -370,7 +372,7 @@ def train_single_epoch( # Get gpu memory usage in GB gpu_mem_usage = torch.cuda.max_memory_allocated() / (1024**3) if compute_descriptors: - print( + logging.info( "Epoch [%d / %d] Iter [%d / %d] loss=%.4f (%.4f), junc_loss=%.4f (%.4f), heatmap_loss=%.4f (%.4f), descriptor_loss=%.4f (%.4f), gpu_mem=%.4fGB" % ( epoch, @@ -389,7 +391,7 @@ def train_single_epoch( ) ) else: - print( + logging.info( "Epoch [%d / %d] Iter [%d / %d] loss=%.4f (%.4f), junc_loss=%.4f (%.4f), heatmap_loss=%.4f (%.4f), gpu_mem=%.4fGB" % ( epoch, @@ -405,7 +407,7 @@ def train_single_epoch( gpu_mem_usage, ) ) - print( + logging.info( "\t Junction precision=%.4f (%.4f) / recall=%.4f (%.4f)" % ( results["junc_precision"], @@ -414,7 +416,7 @@ def train_single_epoch( average["junc_recall"], ) ) - print( + logging.info( "\t Junction nms precision=%.4f (%.4f) / recall=%.4f (%.4f)" % ( results["junc_precision_nms"], @@ -423,7 +425,7 @@ def train_single_epoch( average["junc_recall_nms"], ) ) - print( + logging.info( "\t Heatmap precision=%.4f (%.4f) / recall=%.4f (%.4f)" % ( results["heatmap_precision"], @@ -433,7 +435,7 @@ def train_single_epoch( ) ) if compute_descriptors: - print( + logging.info( "\t Descriptors matching score=%.4f (%.4f)" % (results["matching_score"], average["matching_score"]) ) @@ -629,7 +631,7 @@ def validate( results = metric_func.metric_results average = average_meter.average() if compute_descriptors: - print( + logging.info( "Iter [%d / %d] loss=%.4f (%.4f), junc_loss=%.4f (%.4f), heatmap_loss=%.4f (%.4f), descriptor_loss=%.4f (%.4f)" % ( idx, @@ -645,7 +647,7 @@ def validate( ) ) else: - print( + logging.info( "Iter [%d / %d] loss=%.4f (%.4f), junc_loss=%.4f (%.4f), heatmap_loss=%.4f (%.4f)" % ( idx, @@ -658,7 +660,7 @@ def validate( average["heatmap_loss"], ) ) - print( + logging.info( "\t Junction precision=%.4f (%.4f) / recall=%.4f (%.4f)" % ( results["junc_precision"], @@ -667,7 +669,7 @@ def validate( average["junc_recall"], ) ) - print( + logging.info( "\t Junction nms precision=%.4f (%.4f) / recall=%.4f (%.4f)" % ( results["junc_precision_nms"], @@ -676,7 +678,7 @@ def validate( average["junc_recall_nms"], ) ) - print( + logging.info( "\t Heatmap precision=%.4f (%.4f) / recall=%.4f (%.4f)" % ( results["heatmap_precision"], @@ -686,7 +688,7 @@ def validate( ) ) if compute_descriptors: - print( + logging.info( "\t Descriptors matching score=%.4f (%.4f)" % (results["matching_score"], average["matching_score"]) ) diff --git a/limap/line2d/TP_LSD/tp_lsd.py b/limap/line2d/TP_LSD/tp_lsd.py index f9a67065..73114199 100644 --- a/limap/line2d/TP_LSD/tp_lsd.py +++ b/limap/line2d/TP_LSD/tp_lsd.py @@ -1,3 +1,4 @@ +import logging import os import cv2 @@ -7,11 +8,14 @@ from tp_lsd.utils.reconstruct import TPS_line from tp_lsd.utils.utils import load_model -from ..base_detector import BaseDetector, BaseDetectorOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) class TPLSDDetector(BaseDetector): - def __init__(self, options=BaseDetectorOptions()): + def __init__(self, options=DefaultDetectorOptions): super().__init__(options) # Load the TP-LSD model head = {"center": 1, "dis": 4, "line": 1} @@ -36,7 +40,7 @@ def download_model(self, path): os.makedirs(os.path.dirname(path)) link = "https://github.com/Siyuada7/TP-LSD/blob/master/pretraineds/Res512.pth?raw=true" cmd = ["wget", link, "-O", path] - print("Downloading TP_LSD model...") + logging.info("Downloading TP_LSD model...") subprocess.run(cmd, check=True) def get_module_name(self): diff --git a/limap/line2d/__init__.py b/limap/line2d/__init__.py index 7c29d288..c988fce9 100644 --- a/limap/line2d/__init__.py +++ b/limap/line2d/__init__.py @@ -1,5 +1,3 @@ -import os - # line utilization functions from .line_utils import * from .register_detector import get_detector, get_extractor diff --git a/limap/line2d/base_detector.py b/limap/line2d/base_detector.py index de7feded..492c5819 100644 --- a/limap/line2d/base_detector.py +++ b/limap/line2d/base_detector.py @@ -13,11 +13,16 @@ class BaseDetectorOptions(NamedTuple): """ Base options for the line detector - :param set_gray: whether to set the image to gray scale (sometimes depending on the detector) - :param max_num_2d_segs: maximum number of detected line segments (default = 3000) - :param do_merge_lines: whether to merge close similar lines at post-processing (default = False) - :param visualize: whether to output visualizations into output folder along with the detections (default = False) - :param weight_path: specify path to load weights (at default, weights will be downloaded to ~/.local) + :param set_gray: whether to set the image to gray scale \ + (sometimes depending on the detector) + :param max_num_2d_segs: maximum number of detected \ + line segments (default = 3000) + :param do_merge_lines: whether to merge close similar \ + lines at post-processing (default = False) + :param visualize: whether to output visualizations into \ + output folder along with the detections (default = False) + :param weight_path: specify path to load weights \ + (at default, weights will be downloaded to ~/.local) """ set_gray: bool = True @@ -27,12 +32,15 @@ class BaseDetectorOptions(NamedTuple): weight_path: str = None +DefaultDetectorOptions = BaseDetectorOptions() + + class BaseDetector: """ Virtual class for line detector """ - def __init__(self, options=BaseDetectorOptions()): + def __init__(self, options=DefaultDetectorOptions): self.set_gray = options.set_gray self.max_num_2d_segs = options.max_num_2d_segs self.do_merge_lines = options.do_merge_lines @@ -52,20 +60,26 @@ def detect(self, camview): Virtual method (for detector) - detect 2D line segments Args: - view (:class:`limap.base.CameraView`): The `limap.base.CameraView` instance corresponding to the image + view (:class:`limap.base.CameraView`): \ + The `limap.base.CameraView` instance corresponding to the image Returns: - :class:`np.array` of shape (N, 5): line detections. Each row corresponds to x1, y1, x2, y2 and score. + :class:`np.array` of shape (N, 5): line detections. \ + Each row corresponds to x1, y1, x2, y2 and score. """ raise NotImplementedError # The functions below are required for extractors def extract(self, camview, segs): """ - Virtual method (for extractor) - extract the features for the detected segments + Virtual method (for extractor) - \ + extract the features for the detected segments Args: - view (:class:`limap.base.CameraView`): The `limap.base.CameraView` instance corresponding to the image - segs: :class:`np.array` of shape (N, 5), line detections. Each row corresponds to x1, y1, x2, y2 and score. Computed from the `detect` method. + view (:class:`limap.base.CameraView`): \ + The `limap.base.CameraView` instance corresponding to the image + segs: :class:`np.array` of shape (N, 5), line detections. \ + Each row corresponds to x1, y1, x2, y2 and score. \ + Computed from the `detect` method. Returns: The extracted feature """ @@ -73,7 +87,8 @@ def extract(self, camview, segs): def get_descinfo_fname(self, descinfo_folder, img_id): """ - Virtual method (for extractor) - Get the target filename of the extracted feature + Virtual method (for extractor) - \ + Get the target filename of the extracted feature Args: descinfo_folder (str): The output folder @@ -85,7 +100,8 @@ def get_descinfo_fname(self, descinfo_folder, img_id): def save_descinfo(self, descinfo_folder, img_id, descinfo): """ - Virtual method (for extractor) - Save the extracted feature to the target folder + Virtual method (for extractor) - \ + Save the extracted feature to the target folder Args: descinfo_folder (str): The output folder @@ -96,7 +112,8 @@ def save_descinfo(self, descinfo_folder, img_id, descinfo): def read_descinfo(self, descinfo_folder, img_id): """ - Virtual method (for extractor) - Read in the extracted feature. Dual function for `save_descinfo`. + Virtual method (for extractor) - Read in the extracted feature. \ + Dual function for `save_descinfo`. Args: descinfo_folder (str): The output folder @@ -109,19 +126,25 @@ def read_descinfo(self, descinfo_folder, img_id): # The functions below are required for double-functioning objects def detect_and_extract(self, camview): """ - Virtual method (for dual-functional class that can perform both detection and extraction) - Detect and extract on a single image + Virtual method (for dual-functional class that can perform both \ + detection and extraction) - Detect and extract on a single image Args: - view (:class:`limap.base.CameraView`): The `limap.base.CameraView` instance corresponding to the image + view (:class:`limap.base.CameraView`): \ + The `limap.base.CameraView` instance corresponding to the image Returns: - segs (:class:`np.array`): of shape (N, 5), line detections. Each row corresponds to x1, y1, x2, y2 and score. Computed from the `detect` method. + segs (:class:`np.array`): of shape (N, 5), line detections. \ + Each row corresponds to x1, y1, x2, y2 and score. \ + Computed from the `detect` method. descinfo: The features extracted from the function `extract` """ raise NotImplementedError def sample_descinfo_by_indexes(self, descinfo, indexes): """ - Virtual method (for dual-functional class that can perform both detection and extraction) - sample descriptors for a subset of images + Virtual method (for dual-functional class that can perform \ + both detection and extraction) - \ + sample descriptors for a subset of images Args: descinfo: The features extracted from the function `extract`. @@ -190,10 +213,14 @@ def detect_all_images(self, output_folder, imagecols, skip_exists=False): Args: output_folder (str): The output folder - imagecols (:class:`limap.base.ImageCollection`): The input image collection + imagecols (:class:`limap.base.ImageCollection`): \ + The input image collection skip_exists (bool): Whether to skip already processed images Returns: - dict[int -> :class:`np.array`]: The line detection for each image indexed by the image id. Each segment is with shape (N, 5). Each row corresponds to x1, y1, x2, y2 and score. + dict[int -> :class:`np.array`]: \ + The line detection for each image indexed by the image id. \ + Each segment is with shape (N, 5). \ + Each row corresponds to x1, y1, x2, y2 and score. """ seg_folder = self.get_segments_folder(output_folder) if not skip_exists: @@ -227,12 +254,17 @@ def extract_all_images( self, output_folder, imagecols, all_2d_segs, skip_exists=False ): """ - Perform line descriptor extraction on all images and save the descriptors. + Line descriptor extraction on all images and save the descriptors. Args: output_folder (str): The output folder. - imagecols (:class:`limap.base.ImageCollection`): The input image collection - all_2d_segs (dict[int -> :class:`np.array`]): The line detection for each image indexed by the image id. Each segment is with shape (N, 5). Each row corresponds to x1, y1, x2, y2 and score. Computed from `detect_all_images` + imagecols (:class:`limap.base.ImageCollection`): \ + The input image collection + all_2d_segs (dict[int -> :class:`np.array`]): \ + The line detection for each image indexed by the image id. \ + Each segment is with shape (N, 5). \ + Each row corresponds to x1, y1, x2, y2 and score. \ + Computed from `detect_all_images` skip_exists (bool): Whether to skip already processed images. Returns: descinfo_folder (str): The path to the saved descriptors. @@ -256,14 +288,19 @@ def detect_and_extract_all_images( self, output_folder, imagecols, skip_exists=False ): """ - Perform line detection and description on all images and save the line segments and descriptors + Perform line detection and description on all images and \ + save the line segments and descriptors Args: output_folder (str): The output folder - imagecols (:class:`limap.base.ImageCollection`): The input image collection + imagecols (:class:`limap.base.ImageCollection`): \ + The input image collection skip_exists (bool): Whether to skip already processed images Returns: - all_segs (dict[int -> :class:`np.array`]): The line detection for each image indexed by the image id. Each segment is with shape (N, 5). Each row corresponds to x1, y1, x2, y2 and score. + all_segs (dict[int -> :class:`np.array`]): \ + The line detection for each image indexed by the image id. \ + Each segment is with shape (N, 5). \ + Each row corresponds to x1, y1, x2, y2 and score. descinfo_folder (str): Path to the extracted descriptors. """ assert self.do_merge_lines diff --git a/limap/line2d/base_matcher.py b/limap/line2d/base_matcher.py index 0306ba7d..79596d48 100644 --- a/limap/line2d/base_matcher.py +++ b/limap/line2d/base_matcher.py @@ -11,10 +11,15 @@ class BaseMatcherOptions(NamedTuple): """ Base options for the line matcher - :param topk: number of top matches for each line (if equal to 0, do mutual nearest neighbor matching) - :param n_neighbors: number of visual neighbors, only for naming the output folder - :param n_jobs: number of jobs at multi-processing (please make sure not to exceed the GPU memory limit with learning methods) - :param weight_path: specify path to load weights (at default, weights will be downloaded to ~/.local) + :param topk: number of top matches for each line \ + (if equal to 0, do mutual nearest neighbor matching) + :param n_neighbors: number of visual neighbors, \ + only for naming the output folder + :param n_jobs: number of jobs at multi-processing \ + (please make sure not to exceed the GPU memory limit \ + with learning methods) + :param weight_path: specify path to load weights \ + (at default, weights will be downloaded to ~/.local) """ topk: int = 10 @@ -23,12 +28,15 @@ class BaseMatcherOptions(NamedTuple): weight_path: str = None +DefaultMatcherOptions = BaseMatcherOptions() + + class BaseMatcher: """ Virtual class for line matcher """ - def __init__(self, extractor, options=BaseMatcherOptions()): + def __init__(self, extractor, options=DefaultMatcherOptions): self.extractor = extractor self.topk = options.topk self.n_neighbors = options.n_neighbors @@ -44,7 +52,8 @@ def get_module_name(self): def match_pair(self, descinfo1, descinfo2): """ - Virtual method (need to be implemented) - match two set of lines based on the descriptors + Virtual method (need to be implemented) - match two set \ + of lines based on the descriptors """ raise NotImplementedError @@ -83,7 +92,9 @@ def save_match(self, matches_folder, idx, matches): Args: matches_folder (str): The output matching folder idx (int): image id - matches (dict[int -> :class:`np.array`]): The output matches for each neighboring image, each with shape (N, 2) + matches (dict[int -> :class:`np.array`]): \ + The output matches for each neighboring image, \ + each with shape (N, 2) """ fname = self.get_match_filename(matches_folder, idx) limapio.save_npy(fname, matches) @@ -96,7 +107,9 @@ def read_match(self, matches_folder, idx): matches_folder (str): The output matching folder idx (int): image id Returns: - matches (dict[int -> :class:`np.array`]): The output matches for each neighboring image, each with shape (N, 2) + matches (dict[int -> :class:`np.array`]): \ + The output matches for each neighboring image, \ + each with shape (N, 2) """ fname = self.get_match_filename(matches_folder, idx) return limapio.read_npy(fname).item() diff --git a/limap/line2d/endpoints/extractor.py b/limap/line2d/endpoints/extractor.py index 39d1b709..8aba7f3c 100644 --- a/limap/line2d/endpoints/extractor.py +++ b/limap/line2d/endpoints/extractor.py @@ -6,11 +6,14 @@ import limap.util.io as limapio from limap.point2d.superpoint.superpoint import SuperPoint -from ..base_detector import BaseDetector, BaseDetectorOptions +from ..base_detector import ( + BaseDetector, + DefaultDetectorOptions, +) class SuperPointEndpointsExtractor(BaseDetector): - def __init__(self, options=BaseDetectorOptions(), device=None): + def __init__(self, options=DefaultDetectorOptions, device=None): super().__init__(options) self.device = "cuda" if device is None else device self.sp = ( diff --git a/limap/line2d/endpoints/matcher.py b/limap/line2d/endpoints/matcher.py index ab041168..9313c5da 100644 --- a/limap/line2d/endpoints/matcher.py +++ b/limap/line2d/endpoints/matcher.py @@ -3,11 +3,14 @@ from limap.point2d.superglue.superglue import SuperGlue -from ..base_matcher import BaseMatcher, BaseMatcherOptions +from ..base_matcher import ( + BaseMatcher, + DefaultMatcherOptions, +) class NNEndpointsMatcher(BaseMatcher): - def __init__(self, extractor, options=BaseMatcherOptions(), device=None): + def __init__(self, extractor, options=DefaultMatcherOptions, device=None): super().__init__(extractor, options) assert self.extractor.get_module_name() == "superpoint_endpoints" self.device = "cuda" if device is None else device @@ -112,7 +115,7 @@ class SuperGlueEndpointsMatcher(BaseMatcher): def __init__( self, extractor, - options=BaseMatcherOptions(), + options=DefaultMatcherOptions, weights="outdoor", device=None, ): diff --git a/limap/line2d/line_utils/merge_lines.py b/limap/line2d/line_utils/merge_lines.py index 2184ec78..afd2d296 100644 --- a/limap/line2d/line_utils/merge_lines.py +++ b/limap/line2d/line_utils/merge_lines.py @@ -5,7 +5,7 @@ def project_point_to_line(line_segs, points): - """Given a list of line segments and a list of points (2D or 3D coordinates), + """Given a list of line segments and a list of points (2D / 3D coordinates), compute the orthogonal projection of all points on all lines. This returns the 1D coordinates of the projection on the line, as well as the list of orthogonal distances.""" diff --git a/limap/line2d/register_matcher.py b/limap/line2d/register_matcher.py index e80bd5f3..32a4c99f 100644 --- a/limap/line2d/register_matcher.py +++ b/limap/line2d/register_matcher.py @@ -7,7 +7,8 @@ def get_matcher(cfg_matcher, extractor, n_neighbors=20, weight_path=None): Args: cfg_matcher: config for line matcher - extractor: line extractor inherited from :class:`limap.line2d.base_matcher.BaseMatcher` + extractor: line extractor inherited from \ + :class:`limap.line2d.base_matcher.BaseMatcher` """ options = BaseMatcherOptions() options = options._replace( diff --git a/limap/merging/merging.py b/limap/merging/merging.py index cc627feb..aff1be97 100644 --- a/limap/merging/merging.py +++ b/limap/merging/merging.py @@ -1,3 +1,5 @@ +import logging + from _limap import _base from _limap import _merging as _mrg @@ -34,8 +36,9 @@ def remerge(linker3d, linetracks, num_outliers=2): if num_tracks == num_tracks_new: break num_tracks = num_tracks_new - print( - f"[LOG] tracks after iterative remerging: {len(new_linetracks)} / {len(linetracks)}" + logging.info( + f"[LOG] tracks after iterative remerging: \ + {len(new_linetracks)} / {len(linetracks)}" ) return new_linetracks diff --git a/limap/optimize/line_refinement/line_refinement.py b/limap/optimize/line_refinement/line_refinement.py index 4484a1d1..ca4625ef 100644 --- a/limap/optimize/line_refinement/line_refinement.py +++ b/limap/optimize/line_refinement/line_refinement.py @@ -1,3 +1,4 @@ +import logging import os import numpy as np @@ -104,11 +105,13 @@ def line_refinement( newdist = evaluator.ComputeDistLine(newtrack.line, n_samples=1000) newratio = evaluator.ComputeInlierRatio(newtrack.line, 0.001) if newdist > dist and newratio < ratio: - print( - f"[DEBUG] t_id = {t_id}, original: dist = {dist * 1000:.4f}, ratio = {ratio:.4f}" + logging.info( + f"[DEBUG] t_id = {t_id}, \ + original: dist = {dist * 1000:.4f}, ratio = {ratio:.4f}" ) - print( - f"[DEBUG] t_id = {t_id}, optimized: dist = {newdist * 1000:.4f}, ratio = {newratio:.4f}" + logging.info( + f"[DEBUG] t_id = {t_id}, optimized: \ + dist = {newdist * 1000:.4f}, ratio = {newratio:.4f}" ) # output diff --git a/limap/optimize/line_refinement/solve.py b/limap/optimize/line_refinement/solve.py index 799bf7c4..bb5cd16e 100644 --- a/limap/optimize/line_refinement/solve.py +++ b/limap/optimize/line_refinement/solve.py @@ -27,7 +27,6 @@ def solve_line_refinement( else: channels = 128 rf_engine_name = f"RefinementEngine_f{dtype[-2:]}_c{channels}" - # print("Refinement type: ", rf_engine_name) rf_engine = getattr(_optimize, rf_engine_name)(rf_config) # initialize track and camview diff --git a/limap/point2d/superglue/superglue.py b/limap/point2d/superglue/superglue.py index f356a1b7..4457bdb4 100644 --- a/limap/point2d/superglue/superglue.py +++ b/limap/point2d/superglue/superglue.py @@ -40,6 +40,7 @@ # --------------------------------------------------------------------*/ # %BANNER_END% +import logging import os from copy import deepcopy from pathlib import Path @@ -224,7 +225,7 @@ def __init__(self, config): if not os.path.isfile(path): self.download_model(path) self.load_state_dict(torch.load(str(path))) - print( + logging.info( 'Loaded SuperGlue model ("{}" weights)'.format( self.config["weights"] ) @@ -236,7 +237,7 @@ def download_model(self, path): if not os.path.exists(os.path.dirname(path)): os.makedirs(os.path.dirname(path)) model_name = os.path.basename(path) - print(f"Downloading SuperGlue model {model_name}...") + logging.info(f"Downloading SuperGlue model {model_name}...") link = f"https://github.com/magicleap/SuperGluePretrainedNetwork/blob/master/models/weights/{model_name}?raw=true" cmd = ["wget", link, "-O", path] subprocess.run(cmd, check=True) @@ -311,7 +312,8 @@ def log_sinkhorn_iterations( def log_optimal_transport( self, scores: torch.Tensor, alpha: torch.Tensor, iters: int ) -> torch.Tensor: - """Perform Differentiable Optimal Transport in Log-space for stability""" + """Perform Differentiable Optimal Transport in Log-space \ + for stability""" b, m, n = scores.shape one = scores.new_tensor(1) ms, ns = (m * one).to(scores), (n * one).to(scores) diff --git a/limap/point2d/superpoint/main.py b/limap/point2d/superpoint/main.py index 84a3b7f1..d11d8cbd 100644 --- a/limap/point2d/superpoint/main.py +++ b/limap/point2d/superpoint/main.py @@ -1,4 +1,5 @@ import collections.abc as collections +import logging import pprint from pathlib import Path from typing import Dict, List, Optional, Union @@ -42,7 +43,7 @@ def run_superpoint( overwrite: bool = False, keypoints=None, ) -> Path: - print( + logging.info( "[SuperPoint] Extracting local features with configuration:" f"\n{pprint.pformat(conf)}" ) @@ -61,7 +62,7 @@ def run_superpoint( ) dataset.names = [n for n in dataset.names if n not in skip_names] if len(dataset.names) == 0: - print("[SuperPoint] Skipping the extraction.") + logging.info("[SuperPoint] Skipping the extraction.") return feature_path device = "cuda" if torch.cuda.is_available() else "cpu" @@ -111,7 +112,8 @@ def run_superpoint( except OSError as error: if "No space left on device" in error.args[0]: raise ValueError( - "[SuperPoint] Out of disk space: storing features on disk can take " + "[SuperPoint] Out of disk space: storing features \ + on disk can take " "significant space, did you enable the as_half flag?" ) del grp, fd[name] @@ -119,5 +121,5 @@ def run_superpoint( del pred - print("[SuperPoint] Finished exporting features.") + logging.info("[SuperPoint] Finished exporting features.") return feature_path diff --git a/limap/point2d/superpoint/superpoint.py b/limap/point2d/superpoint/superpoint.py index ef2a153b..c164ec0f 100644 --- a/limap/point2d/superpoint/superpoint.py +++ b/limap/point2d/superpoint/superpoint.py @@ -40,6 +40,7 @@ # --------------------------------------------------------------------*/ # %BANNER_END% +import logging import os from pathlib import Path @@ -165,7 +166,7 @@ def __init__(self, config): if mk == 0 or mk < -1: raise ValueError('"max_keypoints" must be positive or "-1"') - print("Loaded SuperPoint model") + logging.info("Loaded SuperPoint model") def download_model(self, path): import subprocess @@ -174,7 +175,7 @@ def download_model(self, path): os.makedirs(os.path.dirname(path)) link = "https://github.com/magicleap/SuperPointPretrainedNetwork/blob/master/superpoint_v1.pth?raw=true" cmd = ["wget", link, "-O", path] - print("Downloading SuperPoint model...") + logging.info("Downloading SuperPoint model...") subprocess.run(cmd, check=True) def compute_dense_descriptor(self, data): diff --git a/limap/pointsfm/bundler_reader.py b/limap/pointsfm/bundler_reader.py index 881c5467..9e91676d 100644 --- a/limap/pointsfm/bundler_reader.py +++ b/limap/pointsfm/bundler_reader.py @@ -1,3 +1,4 @@ +import logging import os import imagesize @@ -11,7 +12,7 @@ def ReadModelBundler(bundler_path, list_path, model_path): # read imname_list ################################ list_path = os.path.join(bundler_path, list_path) - print(f"Loading bundler list file {list_path}...") + logging.info(f"Loading bundler list file {list_path}...") with open(list_path) as f: lines = f.readlines() image_names = [line.strip("\n").split(" ")[0] for line in lines] @@ -23,7 +24,7 @@ def ReadModelBundler(bundler_path, list_path, model_path): # read sfm model ################################ model_path = os.path.join(bundler_path, model_path) - print(f"Loading bundler model file {model_path}...") + logging.info(f"Loading bundler model file {model_path}...") with open(model_path) as f: lines = f.readlines() counter = 1 # start from the second line diff --git a/limap/pointsfm/colmap_reader.py b/limap/pointsfm/colmap_reader.py index 7bbb0d7b..e69458b0 100644 --- a/limap/pointsfm/colmap_reader.py +++ b/limap/pointsfm/colmap_reader.py @@ -1,3 +1,4 @@ +import logging import os import sys @@ -29,7 +30,7 @@ def check_exists_colmap_model(model_path): def ReadInfos(colmap_path, model_path="sparse", image_path="images"): - print("Start loading COLMAP sparse reconstruction.") + 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")): @@ -46,7 +47,7 @@ def ReadInfos(colmap_path, model_path="sparse", image_path="images"): raise ValueError( f"Error! The model file does not exist at {model_path}" ) - print(f"Reconstruction loaded. (n_images = {len(colmap_images)})") + logging.info(f"Reconstruction loaded. (n_images = {len(colmap_images)})") # read cameras cameras = {} diff --git a/limap/pointsfm/colmap_sfm.py b/limap/pointsfm/colmap_sfm.py index e6d42bc3..829897f4 100644 --- a/limap/pointsfm/colmap_sfm.py +++ b/limap/pointsfm/colmap_sfm.py @@ -1,4 +1,5 @@ import copy +import logging import os import shutil import subprocess @@ -69,7 +70,8 @@ def run_hloc_matches( """ Inputs: - neighbors: map> to avoid exhaustive matches - - imagecols: optionally use the id mapping from _base.ImageCollection to do the match + - imagecols: optionally use the id mapping from \ + _base.ImageCollection to do the match """ image_path = Path(image_path) from hloc import ( @@ -94,7 +96,8 @@ def run_hloc_matches( if keypoints is not None and keypoints != []: if cfg["descriptor"][:10] != "superpoint": raise ValueError( - "Error! Non-superpoint feature extraction is unfortunately not supported in the current implementation." + "Error! Non-superpoint feature extraction is unfortunately \ + not supported in the current implementation." ) # run superpoint from limap.point2d import run_superpoint @@ -159,7 +162,7 @@ def run_colmap_sfm( ### initialize sparse folder if skip_exists and os.path.exists(output_path): - print("[COLMAP] Skipping mapping") + logging.info("[COLMAP] Skipping mapping") return if os.path.exists(output_path): shutil.rmtree(output_path) @@ -232,7 +235,7 @@ def run_colmap_sfm_with_known_poses( ### initialize sparse folder if skip_exists and os.path.exists(point_triangulation_path): - print("[COLMAP] Skipping point triangulation") + logging.info("[COLMAP] Skipping point triangulation") return Path(point_triangulation_path) if os.path.exists(output_path): shutil.rmtree(output_path) diff --git a/limap/pointsfm/database.py b/limap/pointsfm/database.py index d078f51d..47d2aef6 100644 --- a/limap/pointsfm/database.py +++ b/limap/pointsfm/database.py @@ -31,6 +31,7 @@ # This script is based on an original implementation by True Price. +import logging import sqlite3 import sys @@ -299,7 +300,9 @@ def example_usage(): args = parser.parse_args() if os.path.exists(args.database_path): - print("ERROR: database path already exists -- will not modify it.") + logging.info( + "ERROR: database path already exists -- will not modify it." + ) return # Open the database. diff --git a/limap/pointsfm/functions.py b/limap/pointsfm/functions.py index 1a8404a0..24ab5366 100644 --- a/limap/pointsfm/functions.py +++ b/limap/pointsfm/functions.py @@ -1,3 +1,5 @@ +import logging + from _limap import _pointsfm @@ -39,7 +41,7 @@ def ComputeNeighbors( def compute_metainfos(cfg, model, n_neighbors=20): # get neighbors - print(f"Computing visual neighbors... (n_neighbors = {n_neighbors})") + logging.info(f"Computing visual neighbors... (n_neighbors = {n_neighbors})") neighbors = ComputeNeighbors( model, n_neighbors, diff --git a/limap/pointsfm/read_write_model.py b/limap/pointsfm/read_write_model.py index 11c754dd..cb8aa4ed 100755 --- a/limap/pointsfm/read_write_model.py +++ b/limap/pointsfm/read_write_model.py @@ -31,6 +31,7 @@ import argparse import collections +import logging import os import struct @@ -310,7 +311,8 @@ def write_images_text(images, path): "# Image list with two lines of data per image:\n" + "# IMAGE_ID, QW, QX, QY, QZ, TX, TY, TZ, CAMERA_ID, NAME\n" + "# POINTS2D[] as (X, Y, POINT3D_ID)\n" - + f"# Number of images: {len(images)}, mean observations per image: {mean_observations}\n" + + f"# Number of images: {len(images)}, mean observations \ + per image: {mean_observations}\n" ) with open(path, "w") as fid: @@ -437,8 +439,10 @@ def write_points3D_text(points3D, path): ) / len(points3D) HEADER = ( "# 3D point list with one line of data per point:\n" - + "# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] as (IMAGE_ID, POINT2D_IDX)\n" - + f"# Number of points: {len(points3D)}, mean track length: {mean_track_length}\n" + + "# POINT3D_ID, X, Y, Z, R, G, B, ERROR, TRACK[] \ + as (IMAGE_ID, POINT2D_IDX)\n" + + f"# Number of points: {len(points3D)}, mean track length: \ + {mean_track_length}\n" ) with open(path, "w") as fid: @@ -477,7 +481,7 @@ def detect_model_format(path, ext): and os.path.isfile(os.path.join(path, "images" + ext)) and os.path.isfile(os.path.join(path, "points3D" + ext)) ): - print("Detected model format: '" + ext + "'") + logging.info("Detected model format: '" + ext + "'") return True return False @@ -491,7 +495,7 @@ def read_model(path, ext=""): elif detect_model_format(path, ".txt"): ext = ".txt" else: - print("Provide model format: '.bin' or '.txt'") + logging.info("Provide model format: '.bin' or '.txt'") return if ext == ".txt": @@ -583,9 +587,9 @@ def main(): path=args.input_model, ext=args.input_format ) - print("num_cameras:", len(cameras)) - print("num_images:", len(images)) - print("num_points3D:", len(points3D)) + logging.info("num_cameras:", len(cameras)) + logging.info("num_images:", len(images)) + logging.info("num_points3D:", len(points3D)) if args.output_model is not None: write_model( diff --git a/limap/runners/functions.py b/limap/runners/functions.py index a6a80dc6..1c5a8cc3 100644 --- a/limap/runners/functions.py +++ b/limap/runners/functions.py @@ -1,5 +1,5 @@ +import logging import os -import warnings from tqdm import tqdm @@ -18,11 +18,11 @@ def setup(cfg): folder_load = folder_save cfg["dir_save"] = folder_save cfg["dir_load"] = folder_load - print("[LOG] Output dir: {}".format(cfg["dir_save"])) - print("[LOG] Loading dir: {}".format(cfg["dir_load"])) + logging.info("[LOG] Output dir: {}".format(cfg["dir_save"])) + logging.info("[LOG] Loading dir: {}".format(cfg["dir_load"])) if "weight_path" in cfg and cfg["weight_path"] is not None: cfg["weight_path"] = os.path.expanduser(cfg["weight_path"]) - print("[LOG] weight dir: {}".format(cfg["weight_path"])) + logging.info("[LOG] weight dir: {}".format(cfg["weight_path"])) return cfg @@ -34,23 +34,32 @@ def undistort_images( n_jobs=-1, ): """ - Run undistortion on the images stored in the :class:`limap.base.ImageCollection` instance `imagecols` (only distorted images are undistorted), and store the undistorted images into `output_dir`. The function will return a corresponding `limap.base.ImageCollection` instance for the undistorted images. + Run undistortion on the images stored in the \ + :class:`limap.base.ImageCollection` instance `imagecols` \ + (only distorted images are undistorted), and store the undistorted \ + images into `output_dir`. The function will return a corresponding \ + `limap.base.ImageCollection` instance for the undistorted images. Args: - imagecols (:class:`limap.base.ImageCollection`): Image collection of the images to be undistorted. - output_dir (str): output folder for storing the undistorted images - skip_exists (bool): whether to skip already undistorted images in the output folder. + imagecols (:class:`limap.base.ImageCollection`): \ + Image collection of the images to be undistorted. + output_dir (str): \ + output folder for storing the undistorted images + skip_exists (bool): \ + whether to skip already undistorted images in the output folder. Returns: - :class:`limap.base.ImageCollection`: New image collection for the undistorted images + :class:`limap.base.ImageCollection`: \ + New image collection for the undistorted images """ import limap.base as _base loaded_ids = [] unload_ids = imagecols.get_img_ids() if skip_exists: - print( - f"[LOG] Loading undistorted images (n_images = {imagecols.NumImages()})..." + logging.info( + f"[LOG] Loading undistorted images \ + (n_images = {imagecols.NumImages()})..." ) fname_in = os.path.join(output_dir, fname) if os.path.isfile(fname_in): @@ -69,7 +78,9 @@ def undistort_images( # start undistortion if n_jobs == -1: n_jobs = os.cpu_count() - print(f"[LOG] Start undistorting images (n_images = {len(unload_ids)})...") + logging.info( + f"[LOG] Start undistorting images (n_images = {len(unload_ids)})..." + ) import cv2 import imagesize import joblib @@ -133,15 +144,20 @@ def process(imagecols, img_id): def compute_sfminfos(cfg, imagecols, fname="metainfos.txt"): """ - Compute visual neighbors and robust 3D ranges from COLMAP point triangulation. + Compute visual neighbors and robust 3D ranges from \ + COLMAP point triangulation. Args: - cfg (dict): Configuration, fields refer to :file:`cfgs/examples/pointsfm.yaml` as a minimal example - imagecols (:class:`limap.base.ImageCollection`): image collection for the images of interest, storing intrinsics and triangulated poses + cfg (dict): Configuration, \ + fields refer to :file:`cfgs/examples/pointsfm.yaml` + imagecols (:class:`limap.base.ImageCollection`): \ + image collection for the images of interest, \ + storing intrinsics and triangulated poses Returns: colmap_output_path (str): path to store the colmap output neighbors (dict[int -> list[int]]): visual neighbors for each image - ranges (pair of :class:`np.array`, each of shape (3,)): robust 3D ranges for the scene computed from the sfm point cloud. + ranges (pair of :class:`np.array`, each of shape (3,)): \ + robust 3D ranges for the scene computed from the sfm point cloud. """ import limap.pointsfm as _psfm @@ -182,32 +198,38 @@ def compute_2d_segs(cfg, imagecols, compute_descinfo=True): Detect and desribe 2D lines for each image in the image collection Args: - cfg (dict): Configuration, fields refer to :file:`cfgs/examples/line2d_detect.yaml` as a minimal example - imagecols (:class:`limap.base.ImageCollection`): image collection for the images of interest - compute_descinfo (bool, optional, default=True): whether to extract the line descriptors + cfg (dict): Configuration, \ + fields refer to :file:`cfgs/examples/line2d_detect.yaml` + imagecols (:class:`limap.base.ImageCollection`): \ + image collection for the images of interest + compute_descinfo (bool, optional, default=True): \ + whether to extract the line descriptors Returns: - all_2d_segs (dict[int -> :class:`np.array`], each with shape (N, 4) or (N, 5)): all the line detections for each image + all_2d_segs (dict[int -> :class:`np.array`], each with \ + shape (N, 4) or (N, 5)): all the line detections for each image descinfo_folder (str): folder to store the descriptors """ weight_path = cfg.get("weight_path", None) if "extractor" in cfg["line2d"]: - print( - "[LOG] Start 2D line detection and description (detector = {}, extractor = {}, n_images = {})...".format( + logging.info( + "[LOG] Start 2D line detection and description \ + (detector = {}, extractor = {}, n_images = {})...".format( cfg["line2d"]["detector"]["method"], cfg["line2d"]["extractor"]["method"], imagecols.NumImages(), ) ) else: - print( - "[LOG] Start 2D line detection and description (detector = {}, n_images = {})...".format( + logging.info( + "[LOG] Start 2D line detection and description \ + (detector = {}, n_images = {})...".format( cfg["line2d"]["detector"]["method"], imagecols.NumImages() ) ) import limap.line2d if not imagecols.IsUndistorted(): - warnings.warn("The input images are distorted!", stacklevel=2) + logging.warning("The input images are distorted!", stacklevel=2) basedir = os.path.join( "line_detections", cfg["line2d"]["detector"]["method"] ) @@ -273,7 +295,8 @@ def compute_matches(cfg, descinfo_folder, image_ids, neighbors): Match lines for each image with its visual neighbors Args: - cfg (dict): Configuration, fields refeer to :file:`cfgs/examples/line2d_match.yaml` as a minimal example + cfg (dict): Configuration, \ + fields refeer to :file:`cfgs/examples/line2d_match.yaml` descinfo_folder (str): path to store the descriptors image_ids (list[int]): list of image ids neighbors (dict[int -> list[int]]): visual neighbors for each image @@ -281,8 +304,9 @@ def compute_matches(cfg, descinfo_folder, image_ids, neighbors): matches_folder (str): path to store the computed matches """ weight_path = cfg.get("weight_path", None) - print( - "[LOG] Start matching 2D lines... (extractor = {}, matcher = {}, n_images = {}, n_neighbors = {})".format( + logging.info( + "[LOG] Start matching 2D lines... (extractor = {}, matcher = {}, \ + n_images = {}, n_neighbors = {})".format( cfg["line2d"]["extractor"]["method"], cfg["line2d"]["matcher"]["method"], len(image_ids), @@ -326,14 +350,16 @@ def compute_exhausive_matches(cfg, descinfo_folder, image_ids): Match lines for each image with all the other images exhaustively Args: - cfg (dict): Configuration, fields refeer to :file:`cfgs/examples/line2d_match.yaml` as a minimal example + cfg (dict): Configuration, \ + fields refer to :file:`cfgs/examples/line2d_match.yaml` descinfo_folder (str): path to store the descriptors image_ids (list[int]): list of image ids Returns: matches_folder (str): path to store the computed matches """ - print( - "[LOG] Start exhausive matching 2D lines... (extractor = {}, matcher = {}, n_images = {})".format( + logging.info( + "[LOG] Start exhausive matching 2D lines... \ + (extractor = {}, matcher = {}, n_images = {})".format( cfg["line2d"]["extractor"]["method"], cfg["line2d"]["matcher"]["method"], len(image_ids), diff --git a/limap/runners/functions_structures.py b/limap/runners/functions_structures.py index 01ecae9b..25cc4ea8 100644 --- a/limap/runners/functions_structures.py +++ b/limap/runners/functions_structures.py @@ -1,3 +1,4 @@ +import logging import os import numpy as np @@ -88,7 +89,7 @@ def compute_2d_bipartites_from_colmap( reconstruction["images"], reconstruction["points"], ) - print("Start computing 2D bipartites...") + logging.info("Start computing 2D bipartites...") for img_id, colmap_image in tqdm(colmap_images.items()): n_points = colmap_image.xys.shape[0] indexes = np.arange(0, n_points) diff --git a/limap/runners/line_fitnmerge.py b/limap/runners/line_fitnmerge.py index d5e475b4..b1de3268 100644 --- a/limap/runners/line_fitnmerge.py +++ b/limap/runners/line_fitnmerge.py @@ -1,3 +1,4 @@ +import logging import os import joblib @@ -18,12 +19,20 @@ def fit_3d_segs(all_2d_segs, imagecols, depths, fitting_config): Fit 3D line segments over points produced by depth unprojection Args: - all_2d_segs (dict[int -> :class:`np.adarray`]): All the 2D line segments for each image - imagecols (:class:`limap.base.ImageCollection`): The image collection of all images of interest - depths (dict[int -> :class:`CustomizedDepthReader`], where :class:`CustomizedDepthReader` inherits :class:`limap.base.depth_reader_base.BaseDepthReader`): The depth map readers for each image - fitting_config (dict): Configuration, fields refer to :file:`cfgs/examples/fitting_3Dline.yaml` + all_2d_segs (dict[int -> :class:`np.adarray`]): \ + All the 2D line segments for each image + imagecols (:class:`limap.base.ImageCollection`): \ + The image collection of all images of interest + depths (dict[int -> :class:`CustomizedDepthReader`], \ + where :class:`CustomizedDepthReader` inherits \ + :class:`limap.base.depth_reader_base.BaseDepthReader`): \ + The depth map readers for each image + fitting_config (dict): Configuration, \ + fields refer to :file:`cfgs/examples/fitting_3Dline.yaml` Returns: - output (dict[int -> list[(:class:`np.array`, :class:`np.array`)]]): for each image, output a list of :class:`np.array` pair, representing two endpoints + output (dict[int -> list[(:class:`np.array`, :class:`np.array`)]]): \ + for each image, output a list of :class:`np.array` pair, \ + representing two endpoints """ seg3d_list = [] @@ -68,12 +77,20 @@ def fit_3d_segs_with_points3d( Fit 3D line segments over a set of 3D points Args: - all_2d_segs (dict[int -> :class:`np.adarray`]): All the 2D line segments for each image - imagecols (:class:`limap.base.ImageCollection`): The image collection of all images of interest - p3d_reader (dict[int -> :class:`CustomizedP3DReader`], where :class:`CustomizedP3DReader` inherits :class:`limap.base.p3d_reader_base.BaseP3DReader`): The point cloud readers for each image - fitting_config (dict): Configuration, fields refer to :file:`cfgs/examples/fitting_3Dline.yaml` + all_2d_segs (dict[int -> :class:`np.adarray`]): \ + All the 2D line segments for each image + imagecols (:class:`limap.base.ImageCollection`): \ + The image collection of all images of interest + p3d_reader (dict[int -> :class:`CustomizedP3DReader`], \ + where :class:`CustomizedP3DReader` inherits \ + :class:`limap.base.p3d_reader_base.BaseP3DReader`): \ + The point cloud readers for each image + fitting_config (dict): Configuration, \ + fields refer to :file:`cfgs/examples/fitting_3Dline.yaml` Returns: - output (dict[int -> list[(:class:`np.array`, :class:`np.array`)]]): for each image, output a list of :class:`np.array` pair, representing two endpoints + output (dict[int -> list[(:class:`np.array`, :class:`np.array`)]]): \ + for each image, output a list of :class:`np.array` pair, \ + representing two endpoints """ seg3d_list = [] @@ -118,17 +135,26 @@ def line_fitnmerge(cfg, imagecols, depths, neighbors=None, ranges=None): Line reconstruction over multi-view RGB images given depths Args: - cfg (dict): Configuration. Fields refer to :file:`cfgs/fitnmerge/default.yaml` as an example - imagecols (:class:`limap.base.ImageCollection`): The image collection corresponding to all the images of interest - depths (dict[int -> :class:`CustomizedDepthReader`], where :class:`CustomizedDepthReader` inherits :class:`limap.base.depth_reader_base.BaseDepthReader`): The depth map readers for each image - neighbors (dict[int -> list[int]], optional): visual neighbors for each image. By default we compute neighbor information from the covisibility of COLMAP triangulation. - ranges (pair of :class:`np.array` each of shape (3,), optional): robust 3D ranges for the scene. By default we compute range information from the COLMAP triangulation. + cfg (dict): Configuration. \ + Fields refer to :file:`cfgs/fitnmerge/default.yaml` as an example + imagecols (:class:`limap.base.ImageCollection`): \ + The image collection corresponding to all the images of interest + depths (dict[int -> :class:`CustomizedDepthReader`], \ + where :class:`CustomizedDepthReader` inherits \ + :class:`limap.base.depth_reader_base.BaseDepthReader`): \ + The depth map readers for each image + neighbors (dict[int -> list[int]], optional): \ + visual neighbors for each image. By default we compute neighbor \ + information from the covisibility of COLMAP triangulation. + ranges (pair of :class:`np.array` each of shape (3,), optional): \ + robust 3D ranges for the scene. By default we compute range \ + information from the COLMAP triangulation. Returns: list[:class:`limap.base.LineTrack`]: list of output 3D line tracks """ # assertion check assert imagecols.IsUndistorted() - print(f"[LOG] Number of images: {imagecols.NumImages()}") + logging.info(f"[LOG] Number of images: {imagecols.NumImages()}") cfg = _runners.setup(cfg) detector_name = cfg["line2d"]["detector"]["method"] if cfg["fitting"]["var2d"] == -1: @@ -289,17 +315,27 @@ def line_fitting_with_3Dpoints( Line reconstruction over multi-view images with its point cloud Args: - cfg (dict): Configuration. Fields refer to :file:`cfgs/fitnmerge/default.yaml` as an example - imagecols (:class:`limap.base.ImageCollection`): The image collection corresponding to all the images of interest - p3d_reader (dict[int -> :class:`CustomizedP3DReader`], where :class:`CustomizedP3DReader` inherits :class:`limap.base.p3d_reader_base.BaseP3DReader`): The point cloud readers for each image - neighbors (dict[int -> list[int]], optional): visual neighbors for each image. By default we compute neighbor information from the covisibility of COLMAP triangulation. - ranges (pair of :class:`np.array` each of shape (3,), optional): robust 3D ranges for the scene. By default we compute range information from the COLMAP triangulation. + cfg (dict): Configuration. \ + Fields refer to :file:`cfgs/fitnmerge/default.yaml` as an example + imagecols (:class:`limap.base.ImageCollection`): \ + The image collection corresponding to all the images of interest + p3d_reader (dict[int -> :class:`CustomizedP3DReader`], \ + where :class:`CustomizedP3DReader` inherits \ + :class:`limap.base.p3d_reader_base.BaseP3DReader`): \ + The point cloud readers for each image + neighbors (dict[int -> list[int]], optional): \ + visual neighbors for each image. By default \ + we compute neighbor information from \ + the covisibility of COLMAP triangulation. + ranges (pair of :class:`np.array` each of shape (3,), optional): \ + robust 3D ranges for the scene. By default we compute range \ + information from the COLMAP triangulation. Returns: list[:class:`limap.base.LineTrack`]: list of output 3D line tracks """ # assertion check assert imagecols.IsUndistorted() - print(f"[LOG] Number of images: {imagecols.NumImages()}") + logging.info(f"[LOG] Number of images: {imagecols.NumImages()}") cfg = _runners.setup(cfg) detector_name = cfg["line2d"]["detector"]["method"] if cfg["fitting"]["var2d"] == -1: diff --git a/limap/runners/line_localization.py b/limap/runners/line_localization.py index cab3dbf1..17221688 100644 --- a/limap/runners/line_localization.py +++ b/limap/runners/line_localization.py @@ -1,3 +1,4 @@ +import logging import os from collections import defaultdict @@ -30,7 +31,8 @@ def get_hloc_keypoints( if ref_sfm is None or features_path is None or matches_path is None: if logger: logger.debug( - "Not retrieving keypoint correspondences because at least one parameter is not provided." + "Not retrieving keypoint correspondences because \ + at least one parameter is not provided." ) return np.array([]), np.array([]) @@ -101,23 +103,40 @@ def line_localization( logger=None, ): """ - Run visual localization on query images with `imagecols`, it takes 2D-3D point correspondences from HLoc; - runs line matching using 2D line matcher ("epipolar" for Gao et al. "Pose Refinement with Joint Optimization of Visual Points and Lines"); - calls :func:`~limap.estimators.absolute_pose.pl_estimate_absolute_pose` to estimate the absolute camera pose for all query images, + Run visual localization on query images with `imagecols`, \ + it takes 2D-3D point correspondences from HLoc; + runs line matching using 2D line matcher ("epipolar" for Gao et al. \ + "Pose Refinement with Joint Optimization of Visual Points and Lines"); + calls :func:`~limap.estimators.absolute_pose.pl_estimate_absolute_pose` \ + to estimate the absolute camera pose for all query images, and writes results in results file in `results_path`. Args: - cfg (dict): Configuration, fields refer to :file:`cfgs/localization/default.yaml` - imagecols_db (:class:`limap.base.ImageCollection`): Image collection of database images, with triangulated camera poses - imagecols_query (:class:`limap.base.ImageCollection`): Image collection of query images, camera poses only used for epipolar matcher/filter as coarse poses, can be left uninitialized otherwise - linemap_db (list[:class:`limap.base.LineTrack`]): LIMAP triangulated/fitted line tracks - retrieval (dict): Mapping of query image file path to list of neighbor image file paths, e.g. returned from :func:`hloc.utils.parsers.parse_retrieval` - results_path (str | Path): File path to write the localization results - img_name_dict(dict, optional): Mapping of query image IDs to the image file path, by default the image names from `imagecols` - logger (:class:`logging.Logger`, optional): Logger to print logs for information + cfg (dict): Configuration, \ + fields refer to :file:`cfgs/localization/default.yaml` + imagecols_db (:class:`limap.base.ImageCollection`): \ + Image collection of database images, with triangulated camera poses + imagecols_query (:class:`limap.base.ImageCollection`): \ + Image collection of query images, camera poses only used for \ + epipolar matcher/filter as coarse poses, \ + can be left uninitialized otherwise + linemap_db (list[:class:`limap.base.LineTrack`]): \ + LIMAP triangulated/fitted line tracks + retrieval (dict): Mapping of query image file path to \ + list of neighbor image file paths, e.g. returned from \ + :func:`hloc.utils.parsers.parse_retrieval` + results_path (str | Path): \ + File path to write the localization results + img_name_dict(dict, optional): \ + Mapping of query image IDs to the image file path, by default \ + the image names from `imagecols` + logger (:class:`logging.Logger`, optional): \ + Logger to print logs for information Returns: - Dict[int -> :class:`limap.base.CameraPose`]: Mapping of query image IDs to the localized camera poses for all query images. + Dict[int -> :class:`limap.base.CameraPose`]: \ + Mapping of query image IDs to the localized camera poses \ + for all query images. """ if cfg["localization"]["2d_matcher"] not in [ @@ -155,7 +174,8 @@ def line_localization( img_id: imagecols_db.camimage(img_id).pose for img_id in train_ids } - # line detection of query images, fetch detection of db images (generally already be detected during triangulation) + # line detection of query images, fetch detection of + # db images (generally already be detected during triangulation) all_db_segs, _ = _runners.compute_2d_segs( cfg, imagecols_db, compute_descinfo=False ) @@ -168,7 +188,8 @@ def line_localization( all_db_lines, linemap_db ) - # Do matches for query images and retrieved neighbors for superglue endpoints matcher + # Do matches for query images and retrieved neighbors + # for superglue endpoints matcher if cfg["localization"]["2d_matcher"] != "epipolar": weight_path = cfg.get("weight_path", None) if cfg["localization"]["2d_matcher"] == "superglue_endpoints": @@ -225,7 +246,7 @@ def line_localization( ) # Localization - print("[LOG] Starting localization with points+lines...") + logging.info("[LOG] Starting localization with points+lines...") final_poses = {} pose_dir = results_path.parent / "poses_{}".format( cfg["localization"]["2d_matcher"] @@ -300,7 +321,8 @@ def line_localization( query_line_id, track_id = pair all_line_pairs_2to3[query_line_id].append(track_id) - # filter based on reprojection distance (to 1-1 correspondences), mainly for "OPPO method" + # filter based on reprojection distance (to 1-1 correspondences), + # mainly for "OPPO method" if cfg["localization"]["reprojection_filter"] is not None: line_matches_2to3 = reprojection_filter_matches_2to3( query_lines, @@ -322,7 +344,8 @@ def line_localization( num_matches_line = len(line_matches_2to3) if logger: logger.info( - f"{num_matches_line} line matches found for {len(query_lines)} 2D lines" + f"{num_matches_line} line matches found \ + for {len(query_lines)} 2D lines" ) l3ds = [track.line for track in linemap_db] diff --git a/limap/runners/line_triangulation.py b/limap/runners/line_triangulation.py index 72ac719f..2738b7ba 100644 --- a/limap/runners/line_triangulation.py +++ b/limap/runners/line_triangulation.py @@ -1,3 +1,4 @@ +import logging import os from tqdm import tqdm @@ -18,14 +19,20 @@ def line_triangulation(cfg, imagecols, neighbors=None, ranges=None): Main interface of line triangulation over multi-view images. Args: - cfg (dict): Configuration. Fields refer to :file:`cfgs/triangulation/default.yaml` as an example - imagecols (:class:`limap.base.ImageCollection`): The image collection corresponding to all the images of interest - neighbors (dict[int -> list[int]], optional): visual neighbors for each image. By default we compute neighbor information from the covisibility of COLMAP triangulation. - ranges (pair of :class:`np.array` each of shape (3,), optional): robust 3D ranges for the scene. By default we compute range information from the COLMAP triangulation. + cfg (dict): Configuration. \ + Refer to :file:`cfgs/triangulation/default.yaml` as an example + imagecols (:class:`limap.base.ImageCollection`): \ + The image collection corresponding to all the images of interest + neighbors (dict[int -> list[int]], optional): \ + visual neighbors for each image. By default we compute \ + neighbor information from the covisibility of COLMAP triangulation. + ranges (pair of :class:`np.array` each of shape (3,), optional): \ + robust 3D ranges for the scene. By default we compute \ + range information from the COLMAP triangulation. Returns: list[:class:`limap.base.LineTrack`]: list of output 3D line tracks """ - print(f"[LOG] Number of images: {imagecols.NumImages()}") + logging.info(f"[LOG] Number of images: {imagecols.NumImages()}") cfg = _runners.setup(cfg) detector_name = cfg["line2d"]["detector"]["method"] if cfg["triangulation"]["var2d"] == -1: @@ -146,7 +153,7 @@ def line_triangulation(cfg, imagecols, neighbors=None, ranges=None): if cfg["triangulation"]["use_pointsfm"]["use_triangulated_points"]: Triangulator.SetSfMPoints(sfm_points) # triangulate - print("Start multi-view triangulation...") + logging.info("Start multi-view triangulation...") for img_id in tqdm(imagecols.get_img_ids()): if cfg["triangulation"]["use_exhaustive_matcher"]: Triangulator.TriangulateImageExhaustiveMatch( diff --git a/limap/triangulation/triangulation.py b/limap/triangulation/triangulation.py index 4b03d34e..4aef5f37 100644 --- a/limap/triangulation/triangulation.py +++ b/limap/triangulation/triangulation.py @@ -46,7 +46,8 @@ def compute_fundamental_matrix(view1, view2): def compute_epipolar_IoU(l1, view1, l2, view2): """ - Get the IoU between two lines from different views by intersecting the epipolar lines + Get the IoU between two lines from different views by \ + intersecting the epipolar lines Args: l1 (:class:`limap.base.Line2d`) @@ -76,7 +77,8 @@ def point_triangulation(p1, view1, p2, view2): def triangulate_endpoints(l1, view1, l2, view2): """ - Two-view triangulation of lines with point triangulation on both endpoints (assuming correspondences) + Two-view triangulation of lines with point triangulation \ + on both endpoints (assuming correspondences) Args: l1 (:class:`limap.base.Line2d`) diff --git a/limap/undistortion/undistort.py b/limap/undistortion/undistort.py index 2073f6b6..98094b0d 100644 --- a/limap/undistortion/undistort.py +++ b/limap/undistortion/undistort.py @@ -4,10 +4,13 @@ def UndistortImageCamera(camera, imname_in, imname_out): """ - Run COLMAP undistortion on one single image with an input camera. The undistortion is only applied if the camera model is neither "SIMPLE_PINHOLE" nor "PINHOLE". + Run COLMAP undistortion on one single image with an input camera. \ + The undistortion is only applied if the camera model is \ + neither "SIMPLE_PINHOLE" nor "PINHOLE". Args: - camera (:class:`limap.base.Camera`): The camera (type + parameters) for the image. + camera (:class:`limap.base.Camera`): \ + The camera (type + parameters) for the image. imname_in (str): filename for the input image imname_out (str): filename for the output undistorted image @@ -49,12 +52,16 @@ def UndistortPoints(points, distorted_camera, undistorted_camera): Run COLMAP undistortion on the keypoints. Args: - points (list[:class:`np.array`]): List of 2D keypoints on the distorted image - distorted_camera (:class:`limap.base.Camera`): The camera before undistortion - undistorted_camera (:class:`limap.base.Camera`): The camera after undistortion + points (list[:class:`np.array`]): \ + List of 2D keypoints on the distorted image + distorted_camera (:class:`limap.base.Camera`): \ + The camera before undistortion + undistorted_camera (:class:`limap.base.Camera`): \ + The camera after undistortion Returns: - list[:class:`np.array`]: List of the corresponding 2D keypoints on the undistorted image + list[:class:`np.array`]: \ + List of the corresponding 2D keypoints on the undistorted image """ return _undistortion._UndistortPoints( points, distorted_camera, undistorted_camera diff --git a/limap/util/evaluation.py b/limap/util/evaluation.py index d50f9244..11899abf 100644 --- a/limap/util/evaluation.py +++ b/limap/util/evaluation.py @@ -1,3 +1,5 @@ +import logging + import cv2 import numpy as np @@ -27,8 +29,10 @@ def eval_imagecols( imagecols, imagecols_gt, max_error=0.01, enable_logging=True ): if enable_logging: - print(f"[LOG EVAL] imagecols.NumImages() = {imagecols.NumImages()}") - print( + logging.info( + f"[LOG EVAL] imagecols.NumImages() = {imagecols.NumImages()}" + ) + logging.info( f"[LOG EVAL] imagecols_gt.NumImages() = {imagecols_gt.NumImages()}" ) _, imagecols_aligned = _base.align_imagecols( @@ -59,8 +63,10 @@ def eval_imagecols_relpose( ) assert len(shared_img_ids) == imagecols.NumImages() if enable_logging: - print(f"[LOG EVAL] imagecols.NumImages() = {imagecols.NumImages()}") - print( + logging.info( + f"[LOG EVAL] imagecols.NumImages() = {imagecols.NumImages()}" + ) + logging.info( f"[LOG EVAL] imagecols_gt.NumImages() = {imagecols_gt.NumImages()}" ) if fill_uninitialized: diff --git a/limap/util/io.py b/limap/util/io.py index 304e1e9c..1f7dfa06 100644 --- a/limap/util/io.py +++ b/limap/util/io.py @@ -1,3 +1,4 @@ +import logging import os import shutil @@ -79,7 +80,7 @@ def read_ply(fname): y = np.asarray(plydata.elements[0].data["y"]) z = np.asarray(plydata.elements[0].data["z"]) points = np.stack([x, y, z], axis=1) - print(f"number of points: {points.shape[0]}") + logging.info(f"number of points: {points.shape[0]}") return points @@ -223,11 +224,12 @@ def save_l3dpp(folder, imagecols, all_2d_segs): assert imagecols.NumImages() == len(all_2d_segs) image_names = imagecols.get_image_name_list() - # TODO make this function general for different input resolution within the set + # TODO make this function general for different input resolution first_cam = imagecols.cam(imagecols.get_cam_ids()[0]) height, width = first_cam.h(), first_cam.w() - # TODO now it is hard-coded here (need to deal with the weird id mapping of Line3D++) + # TODO now it is hard-coded here + # (need to deal with the weird id mapping of Line3D++) mode = "default" if os.path.basename(image_names[0])[0] == "0": # tnt mode = "tnt" @@ -251,7 +253,7 @@ def save_l3dpp(folder, imagecols, all_2d_segs): for line_id in range(n_segments): line = segs[line_id] f.write(f"{line[0]} {line[1]} {line[2]} {line[3]}\n") - print(f"Writing for L3DPP: {fname}") + logging.info(f"Writing for L3DPP: {fname}") def save_txt_linetracks(fname, linetracks, n_visible_views=4): @@ -263,7 +265,7 @@ def save_txt_linetracks(fname, linetracks, n_visible_views=4): linetracks = [ track for track in linetracks if track.count_images() >= n_visible_views ] - print("Writing all linetracks to a single file...") + logging.info("Writing all linetracks to a single file...") n_tracks = len(linetracks) with open(fname, "w") as f: f.write(f"{n_tracks}\n") @@ -273,10 +275,14 @@ def save_txt_linetracks(fname, linetracks, n_visible_views=4): f"{track_id} {track.count_lines()} {track.count_images()}\n" ) f.write( - f"{track.line.start[0]:.10f} {track.line.start[1]:.10f} {track.line.start[2]:.10f}\n" + f"{track.line.start[0]:.10f} \ + {track.line.start[1]:.10f} \ + {track.line.start[2]:.10f}\n" ) f.write( - f"{track.line.end[0]:.10f} {track.line.end[1]:.10f} {track.line.end[2]:.10f}\n" + f"{track.line.end[0]:.10f} \ + {track.line.end[1]:.10f} \ + {track.line.end[2]:.10f}\n" ) for idx in range(track.count_lines()): f.write(f"{track.image_id_list[idx]} ") @@ -290,7 +296,7 @@ def save_folder_linetracks(folder, linetracks): if os.path.exists(folder): shutil.rmtree(folder) os.makedirs(folder) - print(f"Writing linetracks to {folder}...") + logging.info(f"Writing linetracks to {folder}...") n_tracks = len(linetracks) for track_id in tqdm(range(n_tracks)): fname = os.path.join(folder, f"track_{track_id}.txt") @@ -304,7 +310,7 @@ def read_folder_linetracks(folder): for fname in flist: if fname[-4:] == ".txt" and fname[:5] == "track": n_tracks += 1 - print(f"Read linetracks from {folder}...") + logging.info(f"Read linetracks from {folder}...") linetracks = [] for track_id in range(n_tracks): fname = os.path.join(folder, f"track_{track_id}.txt") @@ -422,7 +428,8 @@ def read_lines_from_input(input_file): # exception raise ValueError( - f"Error! File {input_file} not supported. should be txt, obj, or folder to the linetracks." + f"Error! File {input_file} not supported. \ + should be txt, obj, or folder to the linetracks." ) diff --git a/limap/visualize/trackvis/base.py b/limap/visualize/trackvis/base.py index 97902207..7999c75b 100644 --- a/limap/visualize/trackvis/base.py +++ b/limap/visualize/trackvis/base.py @@ -1,3 +1,5 @@ +import logging + import numpy as np from ..vis_utils import test_line_inside_ranges @@ -23,8 +25,15 @@ def report(self): def report_stats(self): counts = np.array(self.counts) - print( - f"[Track Report] (N2, N4, N6, N8, N10, N20, N50) = ({counts[counts >= 2].shape[0]}, {counts[counts >= 4].shape[0]}, {counts[counts >= 6].shape[0]}, {counts[counts >= 8].shape[0]}, {counts[counts >= 10].shape[0]}, {counts[counts >= 20].shape[0]}, {counts[counts >= 50].shape[0]})" + logging.info( + f"[Track Report] (N2, N4, N6, N8, N10, N20, N50) = \ + ({counts[counts >= 2].shape[0]}, \ + {counts[counts >= 4].shape[0]}, \ + {counts[counts >= 6].shape[0]}, \ + {counts[counts >= 8].shape[0]}, \ + {counts[counts >= 10].shape[0]}, \ + {counts[counts >= 20].shape[0]}, \ + {counts[counts >= 50].shape[0]})" ) def report_avg_supports(self, n_visible_views=4): @@ -32,11 +41,13 @@ def report_avg_supports(self, n_visible_views=4): counts_lines = np.array(self.counts_lines) arr = counts[counts >= n_visible_views] arr_lines = counts_lines[counts >= n_visible_views] - print( - f"average supporting images (>= {n_visible_views}): {arr.sum()} / {arr.shape[0]} = {arr.mean():.2f}" + logging.info( + f"average supporting images (>= {n_visible_views}): \ + {arr.sum()} / {arr.shape[0]} = {arr.mean():.2f}" ) - print( - f"average supporting lines (>= {n_visible_views}): {arr_lines.sum()} / {arr_lines.shape[0]} = {arr_lines.mean():.2f}" + logging.info( + f"average supporting lines (>= {n_visible_views}): \ + {arr_lines.sum()} / {arr_lines.shape[0]} = {arr_lines.mean():.2f}" ) def get_counts_np(self): diff --git a/limap/visualize/vis_bipartite.py b/limap/visualize/vis_bipartite.py index 87057250..c1510793 100644 --- a/limap/visualize/vis_bipartite.py +++ b/limap/visualize/vis_bipartite.py @@ -205,7 +205,6 @@ def open3d_draw_bipartite3d_vpline(bpt3d, ranges=None): width=2, name=f"lineset_vp_{vp_id}", ) - # w = open3d_add_line_set(w, nonvp_line_set, color=(0.0, 0.0, 0.0), width=2, name="lineset_nonvp") w.reset_camera_to_default() w.scene_shader = w.UNLIT w.enable_raw_mode(True) diff --git a/limap/visualize/vis_utils.py b/limap/visualize/vis_utils.py index cfe01f35..01f55663 100644 --- a/limap/visualize/vis_utils.py +++ b/limap/visualize/vis_utils.py @@ -1,4 +1,5 @@ import copy +import logging import os import cv2 @@ -103,7 +104,8 @@ def draw_multiscale_matches( assert img_left.ndim == 2 h, w = img_left.shape - # store the matching results of the first and second images into a single image + # store the matching results of the first and second images + # into a single image left_color_img = cv2.cvtColor(img_left, cv2.COLOR_GRAY2BGR) right_color_img = cv2.cvtColor(img_right, cv2.COLOR_GRAY2BGR) r1, g1, b1 = [], [], [] # the line colors @@ -163,7 +165,8 @@ def draw_singlescale_matches(img_left, img_right, matched_segs, mask=None): assert img_left.ndim == 2 h, w = img_left.shape - # store the matching results of the first and second images into a single image + # store the matching results of the first and second images + # into a single image left_color_img = cv2.cvtColor(img_left, cv2.COLOR_GRAY2BGR) right_color_img = cv2.cvtColor(img_right, cv2.COLOR_GRAY2BGR) colors = [] @@ -376,12 +379,14 @@ def report_dist_reprojection(line3d, line2d, camview, prefix=None): ) sensitivity = line3d.sensitivity(camview) if prefix is None: - print( - f"angle = {angle:.4f}, perp = {perp_dist:.4f}, overlap = {overlap:.4f}, sensi = {sensitivity:.4f}" + logging.info( + f"angle = {angle:.4f}, perp = {perp_dist:.4f}, \ + overlap = {overlap:.4f}, sensi = {sensitivity:.4f}" ) else: - print( - f"{prefix}: angle = {angle:.4f}, perp = {perp_dist:.4f}, overlap = {overlap:.4f}, sensi = {sensitivity:.4f}" + logging.info( + f"{prefix}: angle = {angle:.4f}, perp = {perp_dist:.4f}, \ + overlap = {overlap:.4f}, sensi = {sensitivity:.4f}" ) @@ -396,8 +401,9 @@ def visualize_2d_line(fname, imagecols, all_lines_2d, img_id, line_id): def visualize_line_track( imagecols, linetrack, prefix="linetrack", report=False ): - print( - f"[VISUALIZE]: line length: {linetrack.line.length()}, num_supporting_lines: {len(linetrack.image_id_list)}" + logging.info( + f"[VISUALIZE]: line length: {linetrack.line.length()}, \ + num_supporting_lines: {len(linetrack.image_id_list)}" ) for idx, (img_id, line2d) in enumerate( zip(linetrack.image_id_list, linetrack.line2d_list) @@ -409,7 +415,8 @@ def visualize_line_track( linetrack.line, line2d, imagecols.camview(img_id), - prefix=f"Reprojecting to line {idx} (img {img_id}, line {linetrack.line_id_list[idx]})", + prefix=f"Reprojecting to line {idx} (img {img_id}, \ + line {linetrack.line_id_list[idx]})", ) line2d_proj = linetrack.line.projection(imagecols.camview(img_id)) img = draw_segments( diff --git a/limap/vplib/JLinkage/JLinkage.py b/limap/vplib/JLinkage/JLinkage.py index acdc362a..33c58436 100644 --- a/limap/vplib/JLinkage/JLinkage.py +++ b/limap/vplib/JLinkage/JLinkage.py @@ -1,10 +1,10 @@ from _limap import _vplib -from ..base_vp_detector import BaseVPDetector, BaseVPDetectorOptions +from ..base_vp_detector import BaseVPDetector, DefaultVPDetectorOptions class JLinkage(BaseVPDetector): - def __init__(self, cfg_jlinkage, options=BaseVPDetectorOptions()): + def __init__(self, cfg_jlinkage, options=DefaultVPDetectorOptions): super().__init__(options) self.detector = _vplib.JLinkage(cfg_jlinkage) diff --git a/limap/vplib/base_vp_detector.py b/limap/vplib/base_vp_detector.py index 7eb3e513..487a3567 100644 --- a/limap/vplib/base_vp_detector.py +++ b/limap/vplib/base_vp_detector.py @@ -8,14 +8,18 @@ class BaseVPDetectorOptions(NamedTuple): """ Base options for the vanishing point detector - :param n_jobs: number of jobs at multi-processing (please make sure not to exceed the GPU memory limit with learning methods) + :param n_jobs: number of jobs at multi-processing (please make sure \ + not to exceed the GPU memory limit with learning methods) """ n_jobs: int = 1 +DefaultVPDetectorOptions = BaseVPDetectorOptions() + + class BaseVPDetector: - def __init__(self, options=BaseVPDetectorOptions()): + def __init__(self, options=DefaultVPDetectorOptions): self.n_jobs = options.n_jobs # Module name needs to be set @@ -32,7 +36,8 @@ def detect_vp(self, lines, camview=None): Args: lines (list[:class:`limap.base.Line2d`]): list of input 2D lines. - camview (:class:`limap.base.CameraView`): optional, the `limap.base.CameraView` instance corresponding to the image. + camview (:class:`limap.base.CameraView`): optional, \ + the `limap.base.CameraView` instance corresponding to the image. Returns: vpresult type: list[:class:`limap.vplib.VPResult`] """ @@ -43,8 +48,11 @@ def detect_vp_all_images(self, all_lines, camviews=None): Detect vanishing points on multiple images with multiple processes Args: - all_lines (dict[int, list[:class:`limap.base.Line2d`]]): map storing all the lines for each image - camviews (dict[int, :class:`limap.base.CameraView`]): optional, the `limap.base.CameraView` instances, each corresponding to one image + all_lines (dict[int, list[:class:`limap.base.Line2d`]]): \ + map storing all the lines for each image + camviews (dict[int, :class:`limap.base.CameraView`]): \ + optional, the `limap.base.CameraView` instances, \ + each corresponding to one image """ def process(self, lines): diff --git a/limap/vplib/progressivex/progressivex.py b/limap/vplib/progressivex/progressivex.py index f7a23298..e16ae5b8 100644 --- a/limap/vplib/progressivex/progressivex.py +++ b/limap/vplib/progressivex/progressivex.py @@ -4,7 +4,7 @@ import pyprogressivex from _limap import _vplib -from ..base_vp_detector import BaseVPDetector, BaseVPDetectorOptions +from ..base_vp_detector import BaseVPDetector, DefaultVPDetectorOptions ProgressiveXOptions = namedtuple( "ProgressiveXOptions", @@ -14,7 +14,7 @@ class ProgressiveX(BaseVPDetector): - def __init__(self, cfg, options=BaseVPDetectorOptions()): + def __init__(self, cfg, options=DefaultVPDetectorOptions): super().__init__(options) self.options = ProgressiveXOptions() for fld in self.options._fields: diff --git a/ruff.toml b/ruff.toml index 97d44617..d0d9d5b1 100644 --- a/ruff.toml +++ b/ruff.toml @@ -15,12 +15,13 @@ select = [ # isort "I", ] -ignore = ["E501", "F401", "F403", "B008"] +ignore = ["F401", "F403"] [lint.per-file-ignores] "limap/line2d/L2D2/RAL_net_cov.py" = ["SIM"] -"limap/line2d/LineTR/*.py" = ["F405", "F841", "SIM"] -"limap/line2d/SOLD2/model/*.py" = ["UP031", "SIM"] -"limap/line2d/SOLD2/misc/*.py" = ["E741", "UP031", "SIM"] -"limap/line2d/SOLD2/train.py" = ["UP031"] +"limap/line2d/LineTR/*.py" = ["E501", "F405", "F841", "SIM"] +"limap/line2d/SOLD2/model/*.py" = ["E501", "UP031", "SIM"] +"limap/line2d/SOLD2/misc/*.py" = ["E501", "E741", "UP031", "SIM"] +"limap/line2d/SOLD2/train.py" = ["E501", "UP031"] "limap/point2d/superpoint/main.py" = ["B904"] +"limap/pointsfm/database.py" = ["B008", "E501"] diff --git a/runners/cambridge/localization.py b/runners/cambridge/localization.py index 633e0786..4339ddc7 100644 --- a/runners/cambridge/localization.py +++ b/runners/cambridge/localization.py @@ -78,7 +78,6 @@ def parse_config(): "--eval", default=None, type=Path, help="Path to the result file" ) - # arg_parser.add_argument('--colmap_retriangulate', default=False, action='store_true') arg_parser.add_argument( "--num_covis", type=int, diff --git a/runners/hypersim/Hypersim.py b/runners/hypersim/Hypersim.py index eaac49d7..c577ff3a 100644 --- a/runners/hypersim/Hypersim.py +++ b/runners/hypersim/Hypersim.py @@ -71,7 +71,8 @@ class Hypersim: # constants default_h, default_w = 768, 1024 h, w = default_h, default_w - # fov_x = 60 * np.pi / 180 # set fov_x to pi/3 to match DIODE dataset (60 degrees) + # fov_x = 60 * np.pi / 180 + # set fov_x to pi/3 to match DIODE dataset (60 degrees) fov_x = np.pi / 3 # set fov_x to pi/3 to match DIODE dataset (60 degrees) f = w / (2 * np.tan(fov_x / 2)) fov_y = 2 * np.arctan(h / (2 * f)) @@ -84,7 +85,8 @@ def __init__(self, data_dir): # scene to load self.scene_dir = None - self.mpau = None # meters per asset unit, initialized when set_scene_id is called + # meters per asset unit, initialized when set_scene_id is called + self.mpau = None self.cam_id = -1 self.Tvecs, self.Rvecs = None, None diff --git a/runners/hypersim/refine_sfm.py b/runners/hypersim/refine_sfm.py index 8c8706a4..d9051578 100644 --- a/runners/hypersim/refine_sfm.py +++ b/runners/hypersim/refine_sfm.py @@ -33,7 +33,8 @@ def run_scene_hypersim(cfg, dataset, scene_id, cam_id=0): os.path.join(global_dir_save, "imagecols_gt.npy"), imagecols_gt ) colmap_path = os.path.join(cfg["dir_save"], "colmap_sfm") - # _psfm.run_colmap_sfm(cfg["sfm"], imagecols_gt, output_path=colmap_path, skip_exists=cfg["skip_exists"], map_to_original_image_names=False) + # _psfm.run_colmap_sfm(cfg["sfm"], imagecols_gt, output_path=colmap_path, + # skip_exists=cfg["skip_exists"], map_to_original_image_names=False) imagecols, _, _ = _psfm.read_infos_colmap( cfg["sfm"], colmap_path, model_path="sparse/0", image_path="images" ) @@ -70,11 +71,13 @@ def run_scene_hypersim(cfg, dataset, scene_id, cam_id=0): imagecols, imagecols_gt ) print( - f"original: trans: {np.median(trans_errs_orig):.4f}, rot: {np.median(rot_errs_orig):.4f}" + f"original: trans: {np.median(trans_errs_orig):.4f}, \ + rot: {np.median(rot_errs_orig):.4f}" ) trans_errs, rot_errs = limapeval.eval_imagecols(new_imagecols, imagecols_gt) print( - f"optimized: trans: {np.median(trans_errs):.4f}, rot: {np.median(rot_errs):.4f}" + f"optimized: trans: {np.median(trans_errs):.4f}, \ + rot: {np.median(rot_errs):.4f}" ) diff --git a/runners/inloc/utils.py b/runners/inloc/utils.py index 59a87303..8b14aaf0 100644 --- a/runners/inloc/utils.py +++ b/runners/inloc/utils.py @@ -50,7 +50,8 @@ def read_dataset_inloc( names = names_ if logger: logger.info( - f"Found {len(names)} images in {dataset_dir}, excluding CSE scenes: {exclude_CSE}" + f"Found {len(names)} images in {dataset_dir}, \ + excluding CSE scenes: {exclude_CSE}" ) if cfg["info_path"] is None: diff --git a/runners/pointline_association.py b/runners/pointline_association.py index 17052719..746b271b 100644 --- a/runners/pointline_association.py +++ b/runners/pointline_association.py @@ -27,18 +27,21 @@ def report_vp(vpresults, vptracks, print_pairs=False): n_pairs_parallel += 1 if print_pairs: print( - f"[LOG] Parallel pair detected: {i} / {j}, angle = {angle:.2f}" + f"[LOG] Parallel pair detected: {i} / {j}, \ + angle = {angle:.2f}" ) if angle >= 87.0: n_pairs_orthogonal += 1 if print_pairs: print( - f"[LOG] Orthogonal pair detected: {i} / {j}, angle = {angle:.2f}" + f"[LOG] Orthogonal pair detected: {i} / {j}, \ + angle = {angle:.2f}" ) print(f"[LOG] number of VP tracks: {len(vptracks)}") print("[LOG]", [track.length() for track in vptracks]) print( - f"[LOG] parallel pairs: {n_pairs_parallel}, orthogonal pairs: {n_pairs_orthogonal}" + f"[LOG] parallel pairs: {n_pairs_parallel}, \ + orthogonal pairs: {n_pairs_orthogonal}" ) diff --git a/runners/rome16k/statistics.py b/runners/rome16k/statistics.py index d759a12c..37a4988b 100644 --- a/runners/rome16k/statistics.py +++ b/runners/rome16k/statistics.py @@ -33,7 +33,8 @@ def parse_config(): import argparse arg_parser = argparse.ArgumentParser( - description="triangulate 3d lines from specific component of Rome16k (bundler format)." + description="triangulate 3d lines from specific component \ + of Rome16k (bundler format)." ) arg_parser.add_argument( "-c", diff --git a/runners/rome16k/triangulation.py b/runners/rome16k/triangulation.py index bd4b3187..917f9e16 100644 --- a/runners/rome16k/triangulation.py +++ b/runners/rome16k/triangulation.py @@ -49,7 +49,8 @@ def parse_config(): import argparse arg_parser = argparse.ArgumentParser( - description="triangulate 3d lines from specific component of Rome16k (bundler format)." + description="triangulate 3d lines from specific component \ + of Rome16k (bundler format)." ) arg_parser.add_argument( "-c", diff --git a/runners/tests/line2d.py b/runners/tests/line2d.py index 588d0fae..b417861d 100644 --- a/runners/tests/line2d.py +++ b/runners/tests/line2d.py @@ -12,7 +12,9 @@ view2 = limap.base.CameraView( limap.base.Camera("SIMPLE_PINHOLE", hw=(800, 800)), "runners/tests/data/line2d/frame.0000.color.jpg", -) # You can specify the height and width to resize into in the limap.base.Camera instance at initialization. +) +# You can specify the height and width to resize into +# in the limap.base.Camera instance at initialization. detector = limap.line2d.get_detector( {"method": "deeplsd", "skip_exists": False} ) # get a line detector diff --git a/runners/tests/localization.py b/runners/tests/localization.py index 7db098bd..114ea7f7 100644 --- a/runners/tests/localization.py +++ b/runners/tests/localization.py @@ -82,7 +82,8 @@ def parse_args(): "--line_cost_func", type=str, default="PerpendicularDist", - help="Line Cost function for scoring and optimization, default: %(default)s", + help="Line Cost function for scoring and optimization, \ + default: %(default)s", ) args, unknown = arg_parser.parse_known_args() @@ -127,8 +128,10 @@ def main(): log += ( f"Result(P+L) Pose (qvec, tvec): {final_pose.qvec}, {final_pose.tvec}\n" ) - log += f"HLoc(Point) Pose (qvec, tvec): {data['pose_point'].qvec}, {data['pose_point'].tvec}\n" - log += f"GT Pose (qvec, tvec): {data['pose_gt'].qvec}, {data['pose_gt'].tvec}\n\n" + log += f"HLoc(Point) Pose (qvec, tvec): \ + {data['pose_point'].qvec}, {data['pose_point'].tvec}\n" + log += f"GT Pose (qvec, tvec): \ + {data['pose_gt'].qvec}, {data['pose_gt'].tvec}\n\n" R, t = final_pose.R(), final_pose.tvec e_t = np.linalg.norm(-R_gt.T @ t_gt + R.T @ t, axis=0) diff --git a/scripts/eval_hypersim.py b/scripts/eval_hypersim.py index e505b830..ac5a5d3b 100644 --- a/scripts/eval_hypersim.py +++ b/scripts/eval_hypersim.py @@ -61,7 +61,8 @@ def report_error_to_GT(evaluator, lines, vis_err_th=None): print("R: recall, P: precision") for idx, threshold in enumerate(thresholds): print( - f"R / P at {int(threshold * 1000)}mm: {list_recall[idx]:.2f} / {list_precision[idx]:.2f}" + f"R / P at {int(threshold * 1000)}mm: \ + {list_recall[idx]:.2f} / {list_precision[idx]:.2f}" ) return evaluator @@ -282,7 +283,8 @@ def main(): [track.count_lines() for track in linetracks] ) print( - f"supporting images / lines: ({sup_image_counts.mean():.2f} / {sup_line_counts.mean():.2f})" + f"supporting images / lines: ({sup_image_counts.mean():.2f} \ + / {sup_line_counts.mean():.2f})" ) diff --git a/scripts/eval_tnt.py b/scripts/eval_tnt.py index 00d7cd31..1791f652 100644 --- a/scripts/eval_tnt.py +++ b/scripts/eval_tnt.py @@ -32,7 +32,8 @@ def report_error_to_GT(evaluator, lines): list_precision.append(precision) for idx, threshold in enumerate(thresholds): print( - f"R / P at {int(threshold * 1000)}mm: {list_recall[idx]:.2f} / {list_precision[idx]:.2f}" + f"R / P at {int(threshold * 1000)}mm: \ + {list_recall[idx]:.2f} / {list_precision[idx]:.2f}" ) return evaluator @@ -51,7 +52,8 @@ def report_pc_recall_for_GT(evaluator, lines): num_inliers = (point_dists < threshold).sum() point_recall = 100 * num_inliers / n_points print( - f"{int(threshold * 1000):.0f}mm, inliers = {num_inliers}, point recall = {point_recall:.2f}" + f"{int(threshold * 1000):.0f}mm, inliers = {num_inliers}, \ + point recall = {point_recall:.2f}" ) return evaluator @@ -277,7 +279,8 @@ def main(): [track.count_lines() for track in linetracks] ) print( - f"supporting images / lines: ({sup_image_counts.mean():.2f} / {sup_line_counts.mean():.2f})" + f"supporting images / lines: ({sup_image_counts.mean():.2f} \ + / {sup_line_counts.mean():.2f})" ) diff --git a/scripts/format/black.sh b/scripts/format/black.sh new file mode 100755 index 00000000..b4340de8 --- /dev/null +++ b/scripts/format/black.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# This script runs the black Python formatter on the whole repository. + +# Check version +version_string=$(black --version | sed -E 's/^.*(\d+\.\d+-.*).*$/\1/') +expected_version_string='24.10.0' +if [[ "$version_string" =~ "$expected_version_string" ]]; then + echo "black version '$version_string' matches '$expected_version_string'" +else + echo "black version '$version_string' doesn't match '$expected_version_string'" + exit 1 +fi + +# Get all C++ files checked into the repo, excluding submodules +root_folder=$(git rev-parse --show-toplevel) +all_files=$( \ + git ls-tree --full-tree -r --name-only HEAD . \ + | grep "^.*\(\.py\)$" \ + | sed "s~^~$root_folder/~") +num_files=$(echo $all_files | wc -w) +echo "Formatting ${num_files} files" + +# shellcheck disable=SC2086 +black --preview --enable-unstable-feature=string_processing --line-length 80 ${all_files} diff --git a/scripts/tnt_align.py b/scripts/tnt_align.py index a15726d3..c93dd1b2 100644 --- a/scripts/tnt_align.py +++ b/scripts/tnt_align.py @@ -82,7 +82,12 @@ def main(): basepath = os.path.join(colmap_output_path, scene_id, "dense") cmd = "mkdir -p {}".format(os.path.join(basepath, "aligned")) cmd_list.append(cmd) - cmd = "colmap model_aligner --input_path {} --output_path {} --ref_images_path {} --robust_alignment 1 --robust_alignment_max_error {} --transform_path {} --ref_is_gps false".format( + cmd = "colmap model_aligner --input_path {} --output_path {} \ + --ref_images_path {} \ + --robust_alignment 1 \ + --robust_alignment_max_error {} \ + --transform_path {} \ + --ref_is_gps false".format( os.path.join(basepath, "sparse"), os.path.join(basepath, "aligned"), os.path.join(input_meta_path, scene_id, "geo_positions.txt"), @@ -90,7 +95,8 @@ def main(): os.path.join(basepath, "transform.txt"), ) cmd_list.append(cmd) - cmd = "colmap model_converter --input_path {} --output_path {} --output_type PLY".format( + cmd = "colmap model_converter --input_path {} --output_path {} \ + --output_type PLY".format( os.path.join(basepath, "aligned"), os.path.join(basepath, "aligned/points.ply"), ) diff --git a/scripts/tnt_colmap_runner.py b/scripts/tnt_colmap_runner.py index a0c3faa3..e8610ea5 100644 --- a/scripts/tnt_colmap_runner.py +++ b/scripts/tnt_colmap_runner.py @@ -21,16 +21,19 @@ os.makedirs(dense_folder) database_path = os.path.join(output_folder, "database.db") - cmd = f"colmap feature_extractor --database_path {database_path} --image_path {input_folder}" + cmd = f"colmap feature_extractor --database_path {database_path} \ + --image_path {input_folder}" print(cmd) os.system(cmd) cmd = f"colmap exhaustive_matcher --database_path {database_path}" print(cmd) os.system(cmd) - cmd = f"colmap mapper --database_path {database_path} --image_path {input_folder} --output_path {sparse_folder}" + cmd = f"colmap mapper --database_path {database_path} \ + --image_path {input_folder} --output_path {sparse_folder}" print(cmd) os.system(cmd) - cmd = "colmap image_undistorter --image_path {} --input_path {} --output_path {} --output_type COLMAP".format( + cmd = "colmap image_undistorter --image_path {} --input_path {} \ + --output_path {} --output_type COLMAP".format( input_folder, os.path.join(sparse_folder, "0"), dense_folder ) print(cmd) diff --git a/setup.py b/setup.py index 3258576b..9b02b3f5 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,8 @@ def build_extension(self, ext): # The information here can also be placed in setup.cfg - better separation of -# logic and declaration, and simpler if you include description/version in a file. +# logic and declaration, and simpler if you include description/version in +# one file. setup( name="limap", version="1.0.0", diff --git a/visualize_3d_lines.py b/visualize_3d_lines.py index 14f73bfe..b1499484 100644 --- a/visualize_3d_lines.py +++ b/visualize_3d_lines.py @@ -14,7 +14,8 @@ def parse_args(): "--input_dir", type=str, required=True, - help="input line file. Format supported now: .obj, .npy, linetrack folder.", + help="input line file. Format supported now: \ + .obj, .npy, linetrack folder.", ) arg_parser.add_argument( "-nv",