1+ import argparse
2+ import collections .abc as collections
3+ import os
4+ from pathlib import Path
5+ from typing import List , Optional , Union
6+
7+ import numpy as np
8+
9+ from hloc import logger , pairs_from_retrieval
10+ from hloc .utils .io import list_h5_names
11+ from hloc .utils .parsers import parse_image_lists , parse_retrieval
12+
13+
14+ def main (
15+ output : Path ,
16+ image_list : Optional [Union [Path , List [str ]]] = None ,
17+ features : Optional [Path ] = None ,
18+ window_size : Optional [int ] = 10 ,
19+ quadratic_overlap : bool = True ,
20+ use_loop_closure : bool = False ,
21+ retrieval_path : Optional [Union [Path , str ]] = None ,
22+ retrieval_interval : Optional [int ] = 2 ,
23+ num_loc : Optional [int ] = 5 ,
24+ ) -> None :
25+ """
26+ Generate pairs of images based on sequential matching and optional loop closure.
27+ Args:
28+ output (Path): The output file path where the pairs will be saved.
29+ image_list (Optional[Union[Path, List[str]]]):
30+ A path to a file containing a list of images or a list of image names.
31+ features (Optional[Path]):
32+ A path to a feature file containing image features.
33+ window_size (Optional[int]):
34+ The size of the window for sequential matching. Default is 10.
35+ quadratic_overlap (bool):
36+ Whether to use quadratic overlap in sequential matching. Default is True.
37+ use_loop_closure (bool):
38+ Whether to use loop closure for additional matching. Default is False.
39+ retrieval_path (Optional[Union[Path, str]]):
40+ The path to the retrieval file for loop closure.
41+ retrieval_interval (Optional[int]):
42+ The interval for selecting query images for loop closure. Default is 2.
43+ num_loc (Optional[int]):
44+ The number of top retrieval matches to consider for loop closure.
45+ Default is 5.
46+ Raises:
47+ ValueError: If neither image_list nor features are provided,
48+ or if image_list is of an unknown type.
49+ Returns:
50+ None
51+ """
52+ if image_list is not None :
53+ if isinstance (image_list , (str , Path )):
54+ print (image_list )
55+ names_q = parse_image_lists (image_list )
56+ elif isinstance (image_list , collections .Iterable ):
57+ names_q = list (image_list )
58+ else :
59+ raise ValueError (f"Unknown type for image list: { image_list } " )
60+ elif features is not None :
61+ names_q = list_h5_names (features )
62+ else :
63+ raise ValueError ("Provide either a list of images or a feature file." )
64+
65+ pairs = []
66+ N = len (names_q )
67+
68+ for i in range (N - 1 ):
69+ for j in range (i + 1 , min (i + window_size + 1 , N )):
70+ pairs .append ((names_q [i ], names_q [j ]))
71+
72+ if quadratic_overlap :
73+ q = 2 ** (j - i )
74+ if q > window_size and i + q < N :
75+ pairs .append ((names_q [i ], names_q [i + q ]))
76+
77+ if use_loop_closure :
78+ retrieval_pairs_tmp : Path = output .parent / "retrieval-pairs-tmp.txt"
79+
80+ # match mask describes for each image, which images NOT to include in retrevial
81+ # match search I.e., no reason to get retrieval matches for matches
82+ # already included from sequential matching
83+
84+ query_list = names_q [::retrieval_interval ]
85+ M = len (query_list )
86+ match_mask = np .zeros ((M , N ), dtype = bool )
87+
88+ for i in range (M ):
89+ for k in range (window_size + 1 ):
90+ if i * retrieval_interval - k >= 0 and i * retrieval_interval - k < N :
91+ match_mask [i ][i * retrieval_interval - k ] = 1
92+ if i * retrieval_interval + k >= 0 and i * retrieval_interval + k < N :
93+ match_mask [i ][i * retrieval_interval + k ] = 1
94+
95+ if quadratic_overlap :
96+ if (
97+ i * retrieval_interval - 2 ** k >= 0
98+ and i * retrieval_interval - 2 ** k < N
99+ ):
100+ match_mask [i ][i * retrieval_interval - 2 ** k ] = 1
101+ if (
102+ i * retrieval_interval + 2 ** k >= 0
103+ and i * retrieval_interval + 2 ** k < N
104+ ):
105+ match_mask [i ][i * retrieval_interval + 2 ** k ] = 1
106+
107+ pairs_from_retrieval .main (
108+ retrieval_path ,
109+ retrieval_pairs_tmp ,
110+ num_matched = num_loc ,
111+ match_mask = match_mask ,
112+ db_list = names_q ,
113+ query_list = query_list ,
114+ )
115+
116+ retrieval = parse_retrieval (retrieval_pairs_tmp )
117+
118+ for key , val in retrieval .items ():
119+ for match in val :
120+ pairs .append ((key , match ))
121+
122+ os .unlink (retrieval_pairs_tmp )
123+
124+ logger .info (f"Found { len (pairs )} pairs." )
125+ with open (output , "w" ) as f :
126+ f .write ("\n " .join (" " .join ([i , j ]) for i , j in pairs ))
127+
128+
129+ if __name__ == "__main__" :
130+ parser = argparse .ArgumentParser (
131+ description = """
132+ Create a list of image pairs basedon the sequence of images on alphabetic order
133+ """
134+ )
135+ parser .add_argument ("--output" , required = True , type = Path )
136+ parser .add_argument ("--image_list" , type = Path )
137+ parser .add_argument ("--features" , type = Path )
138+ parser .add_argument (
139+ "--overlap" , type = int , default = 10 , help = "Number of overlapping image pairs"
140+ )
141+ parser .add_argument (
142+ "--quadratic_overlap" ,
143+ action = "store_true" ,
144+ help = "Whether to match images against their quadratic neighbors." ,
145+ )
146+ args = parser .parse_args ()
147+ main (** args .__dict__ )
0 commit comments